/* eslint-disable */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 */

/**
 * Registers shapes.
 */
(function () {

  try {

    // Cube Shape, supports size style
    function CubeShape() {
      mxCylinder.call(this);
    }
    mxUtils.extend(CubeShape, mxCylinder);
    CubeShape.prototype.size = 20;
    CubeShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
      const s = Math.max(0, Math.min(w, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)))));

      if (isForeground) {
        path.moveTo(s, h);
        path.lineTo(s, s);
        path.lineTo(0, 0);
        path.moveTo(s, s);
        path.lineTo(w, s);
        path.end();
      } else {
        path.moveTo(0, 0);
        path.lineTo(w - s, 0);
        path.lineTo(w, s);
        path.lineTo(w, h);
        path.lineTo(s, h);
        path.lineTo(0, h - s);
        path.lineTo(0, 0);
        path.close();
        path.end();
      }
    };
    CubeShape.prototype.getLabelMargins = function (rect) {
      if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
        const s = parseFloat(mxUtils.getValue(this.style, 'size', this.size)) * this.scale;

        return new mxRectangle(s, s, 0, 0);
      }

      return null;
    };

    mxCellRenderer.registerShape('cube', CubeShape);

    const tan30 = Math.tan(mxUtils.toRadians(30));
    const tan30Dx = (0.5 - tan30) / 2;

    // Cube Shape, supports size style
    function IsoRectangleShape() {
      mxActor.call(this);
    }
    mxUtils.extend(IsoRectangleShape, mxActor);
    IsoRectangleShape.prototype.size = 20;
    IsoRectangleShape.prototype.redrawPath = function (path, x, y, w, h) {
      const m = Math.min(w, h / tan30);

      path.translate((w - m) / 2, (h - m) / 2 + m / 4);
      path.moveTo(0, 0.25 * m);
      path.lineTo(0.5 * m, m * tan30Dx);
      path.lineTo(m, 0.25 * m);
      path.lineTo(0.5 * m, (0.5 - tan30Dx) * m);
      path.lineTo(0, 0.25 * m);
      path.close();
      path.end();
    };

    mxCellRenderer.registerShape('isoRectangle', IsoRectangleShape);

    // Cube Shape, supports size style
    function IsoCubeShape() {
      mxCylinder.call(this);
    }
    mxUtils.extend(IsoCubeShape, mxCylinder);
    IsoCubeShape.prototype.size = 20;
    IsoCubeShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
      const m = Math.min(w, h / (0.5 + tan30));

      if (isForeground) {
        path.moveTo(0, 0.25 * m);
        path.lineTo(0.5 * m, (0.5 - tan30Dx) * m);
        path.lineTo(m, 0.25 * m);
        path.moveTo(0.5 * m, (0.5 - tan30Dx) * m);
        path.lineTo(0.5 * m, (1 - tan30Dx) * m);
        path.end();
      } else {
        path.translate((w - m) / 2, (h - m) / 2);
        path.moveTo(0, 0.25 * m);
        path.lineTo(0.5 * m, m * tan30Dx);
        path.lineTo(m, 0.25 * m);
        path.lineTo(m, 0.75 * m);
        path.lineTo(0.5 * m, (1 - tan30Dx) * m);
        path.lineTo(0, 0.75 * m);
        path.close();
        path.end();
      }
    };

    mxCellRenderer.registerShape('isoCube', IsoCubeShape);

    // DataStore Shape, supports size style
    function DataStoreShape() {
      mxCylinder.call(this);
    }
    mxUtils.extend(DataStoreShape, mxCylinder);

    DataStoreShape.prototype.redrawPath = function (c, x, y, w, h, isForeground) {
      const dy = Math.min(h / 2, Math.round(h / 8) + this.strokewidth - 1);

      if ((isForeground && this.fill != null) || (!isForeground && this.fill == null)) {
        c.moveTo(0, dy);
        c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);

        // Needs separate shapes for correct hit-detection
        if (!isForeground) {
          c.stroke();
          c.begin();
        }

        c.translate(0, dy / 2);
        c.moveTo(0, dy);
        c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);

        // Needs separate shapes for correct hit-detection
        if (!isForeground) {
          c.stroke();
          c.begin();
        }

        c.translate(0, dy / 2);
        c.moveTo(0, dy);
        c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);

        // Needs separate shapes for correct hit-detection
        if (!isForeground) {
          c.stroke();
          c.begin();
        }

        c.translate(0, -dy);
      }

      if (!isForeground) {
        c.moveTo(0, dy);
        c.curveTo(0, -dy / 3, w, -dy / 3, w, dy);
        c.lineTo(w, h - dy);
        c.curveTo(w, h + dy / 3, 0, h + dy / 3, 0, h - dy);
        c.close();
      }
    };
    DataStoreShape.prototype.getLabelMargins = function (rect) {
      return new mxRectangle(0, 2.5 * Math.min(rect.height / 2, Math.round(rect.height / 8) +
        this.strokewidth - 1) * this.scale, 0, 0);
    };

    mxCellRenderer.registerShape('datastore', DataStoreShape);

    // Note Shape, supports size style
    function NoteShape() {
      mxCylinder.call(this);
    }
    mxUtils.extend(NoteShape, mxCylinder);
    NoteShape.prototype.size = 30;
    NoteShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
      const s = Math.max(0, Math.min(w, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)))));

      if (isForeground) {
        path.moveTo(w - s, 0);
        path.lineTo(w - s, s);
        path.lineTo(w, s);
        path.end();
      } else {
        path.moveTo(0, 0);
        path.lineTo(w - s, 0);
        path.lineTo(w, s);
        path.lineTo(w, h);
        path.lineTo(0, h);
        path.lineTo(0, 0);
        path.close();
        path.end();
      }
    };

    mxCellRenderer.registerShape('note', NoteShape);

    // Note Shape, supports size style
    function SwitchShape() {
      mxActor.call(this);
    }
    mxUtils.extend(SwitchShape, mxActor);
    SwitchShape.prototype.redrawPath = function (c, x, y, w, h) {
      const curve = 0.5;
      c.moveTo(0, 0);
      c.quadTo(w / 2, h * curve, w, 0);
      c.quadTo(w * (1 - curve), h / 2, w, h);
      c.quadTo(w / 2, h * (1 - curve), 0, h);
      c.quadTo(w * curve, h / 2, 0, 0);
      c.end();
    };

    mxCellRenderer.registerShape('switch', SwitchShape);

    // Folder Shape, supports tabWidth, tabHeight styles
    function FolderShape() {
      mxCylinder.call(this);
    }
    mxUtils.extend(FolderShape, mxCylinder);
    FolderShape.prototype.tabWidth = 60;
    FolderShape.prototype.tabHeight = 20;
    FolderShape.prototype.tabPosition = 'right';
    FolderShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
      const dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'tabWidth', this.tabWidth))));
      const dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'tabHeight', this.tabHeight))));
      const tp = mxUtils.getValue(this.style, 'tabPosition', this.tabPosition);

      if (isForeground) {
        if (tp == 'left') {
          path.moveTo(0, dy);
          path.lineTo(dx, dy);
        }
        // Right is default
        else {
          path.moveTo(w - dx, dy);
          path.lineTo(w, dy);
        }

        path.end();
      } else {
        if (tp == 'left') {
          path.moveTo(0, 0);
          path.lineTo(dx, 0);
          path.lineTo(dx, dy);
          path.lineTo(w, dy);
        }
        // Right is default
        else {
          path.moveTo(0, dy);
          path.lineTo(w - dx, dy);
          path.lineTo(w - dx, 0);
          path.lineTo(w, 0);
        }

        path.lineTo(w, h);
        path.lineTo(0, h);
        path.lineTo(0, dy);
        path.close();
        path.end();
      }
    };

    mxCellRenderer.registerShape('folder', FolderShape);

    // Card shape
    function CardShape() {
      mxActor.call(this);
    }
    mxUtils.extend(CardShape, mxActor);
    CardShape.prototype.size = 30;
    CardShape.prototype.isRoundable = function () {
      return true;
    };
    CardShape.prototype.redrawPath = function (c, x, y, w, h) {
      const s = Math.max(0, Math.min(w, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(s, 0), new mxPoint(w, 0), new mxPoint(w, h), new mxPoint(0, h), new mxPoint(0, s)],
        this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('card', CardShape);

    // Tape shape
    function TapeShape() {
      mxActor.call(this);
    }
    mxUtils.extend(TapeShape, mxActor);
    TapeShape.prototype.size = 0.4;
    TapeShape.prototype.redrawPath = function (c, x, y, w, h) {
      const dy = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const fy = 1.4;

      c.moveTo(0, dy / 2);
      c.quadTo(w / 4, dy * fy, w / 2, dy / 2);
      c.quadTo(w * 3 / 4, dy * (1 - fy), w, dy / 2);
      c.lineTo(w, h - dy / 2);
      c.quadTo(w * 3 / 4, h - dy * fy, w / 2, h - dy / 2);
      c.quadTo(w / 4, h - dy * (1 - fy), 0, h - dy / 2);
      c.lineTo(0, dy / 2);
      c.close();
      c.end();
    };

    TapeShape.prototype.getLabelBounds = function (rect) {
      if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
        let size = mxUtils.getValue(this.style, 'size', this.size);
        let w = rect.width;
        let h = rect.height;

        if (this.direction == null ||
          this.direction == mxConstants.DIRECTION_EAST ||
          this.direction == mxConstants.DIRECTION_WEST) {
          let dy = h * size;

          return new mxRectangle(rect.x, rect.y + dy, w, h - 2 * dy);
        }

        let dx = w * size;

        return new mxRectangle(rect.x + dx, rect.y, w - 2 * dx, h);
      }

      return rect;
    };

    mxCellRenderer.registerShape('tape', TapeShape);

    // Document shape
    function DocumentShape() {
      mxActor.call(this);
    }
    mxUtils.extend(DocumentShape, mxActor);
    DocumentShape.prototype.size = 0.3;
    DocumentShape.prototype.getLabelMargins = function (rect) {
      if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
        return new mxRectangle(0, 0, 0, parseFloat(mxUtils.getValue(
          this.style, 'size', this.size,
        )) * rect.height);
      }

      return null;
    };
    DocumentShape.prototype.redrawPath = function (c, x, y, w, h) {
      const dy = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const fy = 1.4;

      c.moveTo(0, 0);
      c.lineTo(w, 0);
      c.lineTo(w, h - dy / 2);
      c.quadTo(w * 3 / 4, h - dy * fy, w / 2, h - dy / 2);
      c.quadTo(w / 4, h - dy * (1 - fy), 0, h - dy / 2);
      c.lineTo(0, dy / 2);
      c.close();
      c.end();
    };

    mxCellRenderer.registerShape('document', DocumentShape);

    const cylinderGetCylinderSize = mxCylinder.prototype.getCylinderSize;

    mxCylinder.prototype.getCylinderSize = function (x, y, w, h) {
      let size = mxUtils.getValue(this.style, 'size');

      if (size != null) {
        return h * Math.max(0, Math.min(1, size));
      }

      return cylinderGetCylinderSize.apply(this, arguments);
    };

    mxCylinder.prototype.getLabelMargins = function (rect) {
      if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
        const size = mxUtils.getValue(this.style, 'size', 0.15) * 2;

        return new mxRectangle(0, Math.min(this.maxHeight * this.scale, rect.height * size), 0, 0);
      }

      return null;
    };

    // Parallelogram shape
    function ParallelogramShape() {
      mxActor.call(this);
    }
    mxUtils.extend(ParallelogramShape, mxActor);
    ParallelogramShape.prototype.size = 0.2;
    ParallelogramShape.prototype.isRoundable = function () {
      return true;
    };
    ParallelogramShape.prototype.redrawPath = function (c, x, y, w, h) {
      const dx = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(0, h), new mxPoint(dx, 0), new mxPoint(w, 0), new mxPoint(w - dx, h)],
        this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('parallelogram', ParallelogramShape);

    // Trapezoid shape
    function TrapezoidShape() {
      mxActor.call(this);
    }
    mxUtils.extend(TrapezoidShape, mxActor);
    TrapezoidShape.prototype.size = 0.2;
    TrapezoidShape.prototype.isRoundable = function () {
      return true;
    };
    TrapezoidShape.prototype.redrawPath = function (c, x, y, w, h) {
      const dx = w * Math.max(0, Math.min(0.5, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(0, h), new mxPoint(dx, 0), new mxPoint(w - dx, 0), new mxPoint(w, h)],
        this.isRounded, arcSize, true);
    };

    mxCellRenderer.registerShape('trapezoid', TrapezoidShape);

    // Curly Bracket shape
    function CurlyBracketShape() {
      mxActor.call(this);
    }
    mxUtils.extend(CurlyBracketShape, mxActor);
    CurlyBracketShape.prototype.size = 0.5;
    CurlyBracketShape.prototype.redrawPath = function (c, x, y, w, h) {
      c.setFillColor(null);
      const s = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(w, 0), new mxPoint(s, 0), new mxPoint(s, h / 2),
        new mxPoint(0, h / 2), new mxPoint(s, h / 2), new mxPoint(s, h),
        new mxPoint(w, h)
      ], this.isRounded, arcSize, false);
      c.end();
    };

    mxCellRenderer.registerShape('curlyBracket', CurlyBracketShape);

    // Parallel marker shape
    function ParallelMarkerShape() {
      mxActor.call(this);
    }
    mxUtils.extend(ParallelMarkerShape, mxActor);
    ParallelMarkerShape.prototype.redrawPath = function (c, x, y, w, h) {
      c.setStrokeWidth(1);
      c.setFillColor(this.stroke);
      const w2 = w / 5;
      c.rect(0, 0, w2, h);
      c.fillAndStroke();
      c.rect(2 * w2, 0, w2, h);
      c.fillAndStroke();
      c.rect(4 * w2, 0, w2, h);
      c.fillAndStroke();
    };

    mxCellRenderer.registerShape('parallelMarker', ParallelMarkerShape);

    /**
     * Adds handJiggle style (jiggle=n sets jiggle)
     */
    function HandJiggle(canvas, defaultVariation) {
      this.canvas = canvas;

      // Avoids "spikes" in the output
      this.canvas.setLineJoin('round');
      this.canvas.setLineCap('round');

      this.defaultVariation = defaultVariation;

      this.originalLineTo = this.canvas.lineTo;
      this.canvas.lineTo = mxUtils.bind(this, HandJiggle.prototype.lineTo);

      this.originalMoveTo = this.canvas.moveTo;
      this.canvas.moveTo = mxUtils.bind(this, HandJiggle.prototype.moveTo);

      this.originalClose = this.canvas.close;
      this.canvas.close = mxUtils.bind(this, HandJiggle.prototype.close);

      this.originalQuadTo = this.canvas.quadTo;
      this.canvas.quadTo = mxUtils.bind(this, HandJiggle.prototype.quadTo);

      this.originalCurveTo = this.canvas.curveTo;
      this.canvas.curveTo = mxUtils.bind(this, HandJiggle.prototype.curveTo);

      this.originalArcTo = this.canvas.arcTo;
      this.canvas.arcTo = mxUtils.bind(this, HandJiggle.prototype.arcTo);
    }

    HandJiggle.prototype.moveTo = function (endX, endY) {
      this.originalMoveTo.apply(this.canvas, arguments);
      this.lastX = endX;
      this.lastY = endY;
      this.firstX = endX;
      this.firstY = endY;
    };

    HandJiggle.prototype.close = function () {
      if (this.firstX != null && this.firstY != null) {
        this.lineTo(this.firstX, this.firstY);
        this.originalClose.apply(this.canvas, arguments);
      }

      this.originalClose.apply(this.canvas, arguments);
    };

    HandJiggle.prototype.quadTo = function (x1, y1, x2, y2) {
      this.originalQuadTo.apply(this.canvas, arguments);
      this.lastX = x2;
      this.lastY = y2;
    };

    HandJiggle.prototype.curveTo = function (x1, y1, x2, y2, x3, y3) {
      this.originalCurveTo.apply(this.canvas, arguments);
      this.lastX = x3;
      this.lastY = y3;
    };

    HandJiggle.prototype.arcTo = function (rx, ry, angle, largeArcFlag, sweepFlag, x, y) {
      this.originalArcTo.apply(this.canvas, arguments);
      this.lastX = x;
      this.lastY = y;
    };

    HandJiggle.prototype.lineTo = function (endX, endY) {
      // LATER: Check why this.canvas.lastX cannot be used
      if (this.lastX != null && this.lastY != null) {
        const dx = Math.abs(endX - this.lastX);
        const dy = Math.abs(endY - this.lastY);
        const dist = Math.sqrt(dx * dx + dy * dy);

        if (dist < 2) {
          this.originalLineTo.apply(this.canvas, arguments);
          this.lastX = endX;
          this.lastY = endY;

          return;
        }

        let segs = Math.round(dist / 10);
        let variation = this.defaultVariation;

        if (segs < 5) {
          segs = 5;
          variation /= 3;
        }

        function sign(x) {
          return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? 0 : NaN : NaN;
        }

        const stepX = sign(endX - this.lastX) * dx / segs;
        const stepY = sign(endY - this.lastY) * dy / segs;

        const fx = dx / dist;
        const fy = dy / dist;

        for (let s = 0; s < segs; s++) {
          const x = stepX * s + this.lastX;
          const y = stepY * s + this.lastY;

          const offset = (Math.random() - 0.5) * variation;
          this.originalLineTo.call(this.canvas, x - offset * fy, y - offset * fx);
        }

        this.originalLineTo.call(this.canvas, endX, endY);
        this.lastX = endX;
        this.lastY = endY;
      } else {
        this.originalLineTo.apply(this.canvas, arguments);
        this.lastX = endX;
        this.lastY = endY;
      }
    };

    HandJiggle.prototype.destroy = function () {
      this.canvas.lineTo = this.originalLineTo;
      this.canvas.moveTo = this.originalMoveTo;
      this.canvas.close = this.originalClose;
      this.canvas.quadTo = this.originalQuadTo;
      this.canvas.curveTo = this.originalCurveTo;
      this.canvas.arcTo = this.originalArcTo;
    };

    // Installs hand jiggle in all shapes
    const mxShapePaint0 = mxShape.prototype.paint;
    mxShape.prototype.defaultJiggle = 1.5;
    mxShape.prototype.paint = function (c) {
      // NOTE: getValue does not return a boolean value so !('0') would return true here and below
      if (this.style != null && mxUtils.getValue(this.style, 'comic', '0') != '0' && c.handHiggle == null) {
        c.handJiggle = new HandJiggle(c, mxUtils.getValue(this.style, 'jiggle', this.defaultJiggle));
      }

      mxShapePaint0.apply(this, arguments);

      if (c.handJiggle != null) {
        c.handJiggle.destroy();
        delete c.handJiggle;
      }
    };

    // Sets default jiggle for diamond
    mxRhombus.prototype.defaultJiggle = 2;

    /**
     * Overrides to avoid call to rect
     */
    const mxRectangleShapeIsHtmlAllowed0 = mxRectangleShape.prototype.isHtmlAllowed;
    mxRectangleShape.prototype.isHtmlAllowed = function () {
      return (this.style == null || mxUtils.getValue(this.style, 'comic', '0') == '0') &&
        mxRectangleShapeIsHtmlAllowed0.apply(this, arguments);
    };

    const mxRectangleShapePaintBackground0 = mxRectangleShape.prototype.paintBackground;
    mxRectangleShape.prototype.paintBackground = function (c, x, y, w, h) {
      if (c.handJiggle == null) {
        mxRectangleShapePaintBackground0.apply(this, arguments);
      } else {
        let events = true;

        if (this.style != null) {
          events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
        }

        if (events || (this.fill != null && this.fill != mxConstants.NONE) ||
          (this.stroke != null && this.stroke != mxConstants.NONE)) {
          if (!events && (this.fill == null || this.fill == mxConstants.NONE)) {
            c.pointerEvents = false;
          }

          c.begin();

          if (this.isRounded) {
            let r = 0;

            if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
              r = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style,
                mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2));
            } else {
              const f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
                mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
              r = Math.min(w * f, h * f);
            }

            c.moveTo(x + r, y);
            c.lineTo(x + w - r, y);
            c.quadTo(x + w, y, x + w, y + r);
            c.lineTo(x + w, y + h - r);
            c.quadTo(x + w, y + h, x + w - r, y + h);
            c.lineTo(x + r, y + h);
            c.quadTo(x, y + h, x, y + h - r);
            c.lineTo(x, y + r);
            c.quadTo(x, y, x + r, y);
          } else {
            c.moveTo(x, y);
            c.lineTo(x + w, y);
            c.lineTo(x + w, y + h);
            c.lineTo(x, y + h);
            c.lineTo(x, y);
          }

          // LATER: Check if close is needed here
          c.close();
          c.end();

          c.fillAndStroke();
        }
      }
    };

    /**
     * Disables glass effect with hand jiggle.
     */
    const mxRectangleShapePaintForeground0 = mxRectangleShape.prototype.paintForeground;
    mxRectangleShape.prototype.paintForeground = function (c, x, y, w, h) {
      if (c.handJiggle == null) {
        mxRectangleShapePaintForeground0.apply(this, arguments);
      }
    };

    // End of hand jiggle integration

    // Process Shape
    function ProcessShape() {
      mxRectangleShape.call(this);
    }
    mxUtils.extend(ProcessShape, mxRectangleShape);
    ProcessShape.prototype.size = 0.1;
    ProcessShape.prototype.isHtmlAllowed = function () {
      return false;
    };
    ProcessShape.prototype.getLabelBounds = function (rect) {
      if (mxUtils.getValue(this.state.style, mxConstants.STYLE_HORIZONTAL, true) ==
        (this.direction == null ||
          this.direction == mxConstants.DIRECTION_EAST ||
          this.direction == mxConstants.DIRECTION_WEST)) {
        const w = rect.width;
        const h = rect.height;
        const r = new mxRectangle(rect.x, rect.y, w, h);

        let inset = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));

        if (this.isRounded) {
          const f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
            mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
          inset = Math.max(inset, Math.min(w * f, h * f));
        }

        r.x += Math.round(inset);
        r.width -= Math.round(2 * inset);

        return r;
      }

      return rect;
    };
    ProcessShape.prototype.paintForeground = function (c, x, y, w, h) {
      let inset = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));

      if (this.isRounded) {
        const f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
          mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
        inset = Math.max(inset, Math.min(w * f, h * f));
      }

      // Crisp rendering of inner lines
      inset = Math.round(inset);

      c.begin();
      c.moveTo(x + inset, y);
      c.lineTo(x + inset, y + h);
      c.moveTo(x + w - inset, y);
      c.lineTo(x + w - inset, y + h);
      c.end();
      c.stroke();
      mxRectangleShape.prototype.paintForeground.apply(this, arguments);
    };

    mxCellRenderer.registerShape('process', ProcessShape);

    // Transparent Shape
    function TransparentShape() {
      mxRectangleShape.call(this);
    }
    mxUtils.extend(TransparentShape, mxRectangleShape);
    TransparentShape.prototype.paintBackground = function (c, x, y, w, h) {
      c.setFillColor(mxConstants.NONE);
      c.rect(x, y, w, h);
      c.fill();
    };
    TransparentShape.prototype.paintForeground = function (c, x, y, w, h) {};

    mxCellRenderer.registerShape('transparent', TransparentShape);

    // Callout shape
    function CalloutShape() {
      mxActor.call(this);
    }
    mxUtils.extend(CalloutShape, mxHexagon);
    CalloutShape.prototype.size = 30;
    CalloutShape.prototype.position = 0.5;
    CalloutShape.prototype.position2 = 0.5;
    CalloutShape.prototype.base = 20;
    CalloutShape.prototype.getLabelMargins = function () {
      return new mxRectangle(0, 0, 0, parseFloat(mxUtils.getValue(
        this.style, 'size', this.size,
      )) * this.scale);
    };
    CalloutShape.prototype.isRoundable = function () {
      return true;
    };
    CalloutShape.prototype.redrawPath = function (c, x, y, w, h) {
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      const s = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const dx = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'position', this.position))));
      const dx2 = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'position2', this.position2))));
      const base = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'base', this.base))));

      this.addPoints(c, [new mxPoint(0, 0), new mxPoint(w, 0), new mxPoint(w, h - s),
          new mxPoint(Math.min(w, dx + base), h - s), new mxPoint(dx2, h),
          new mxPoint(Math.max(0, dx), h - s), new mxPoint(0, h - s)
        ],
        this.isRounded, arcSize, true, [4]);
    };

    mxCellRenderer.registerShape('callout', CalloutShape);

    // Step shape
    function StepShape() {
      mxActor.call(this);
    }
    mxUtils.extend(StepShape, mxActor);
    StepShape.prototype.size = 0.2;
    StepShape.prototype.fixedSize = 20;
    StepShape.prototype.isRoundable = function () {
      return true;
    };
    StepShape.prototype.redrawPath = function (c, x, y, w, h) {
      const fixed = mxUtils.getValue(this.style, 'fixedSize', '0') != '0';
      const s = (fixed) ? Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'size', this.fixedSize)))) :
        w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(0, 0), new mxPoint(w - s, 0), new mxPoint(w, h / 2), new mxPoint(w - s, h),
        new mxPoint(0, h), new mxPoint(s, h / 2)
      ], this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('step', StepShape);

    // Hexagon shape
    function HexagonShape() {
      mxActor.call(this);
    }
    mxUtils.extend(HexagonShape, mxHexagon);
    HexagonShape.prototype.size = 0.25;
    HexagonShape.prototype.isRoundable = function () {
      return true;
    };
    HexagonShape.prototype.redrawPath = function (c, x, y, w, h) {
      const s = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(s, 0), new mxPoint(w - s, 0), new mxPoint(w, 0.5 * h), new mxPoint(w - s, h),
        new mxPoint(s, h), new mxPoint(0, 0.5 * h)
      ], this.isRounded, arcSize, true);
    };

    mxCellRenderer.registerShape('hexagon', HexagonShape);

    // Plus Shape
    function PlusShape() {
      mxRectangleShape.call(this);
    }
    mxUtils.extend(PlusShape, mxRectangleShape);
    PlusShape.prototype.isHtmlAllowed = function () {
      return false;
    };
    PlusShape.prototype.paintForeground = function (c, x, y, w, h) {
      const border = Math.min(w / 5, h / 5) + 1;

      c.begin();
      c.moveTo(x + w / 2, y + border);
      c.lineTo(x + w / 2, y + h - border);
      c.moveTo(x + border, y + h / 2);
      c.lineTo(x + w - border, y + h / 2);
      c.end();
      c.stroke();
      mxRectangleShape.prototype.paintForeground.apply(this, arguments);
    };

    mxCellRenderer.registerShape('plus', PlusShape);

    // Overrides painting of rhombus shape to allow for double style
    const mxRhombusPaintVertexShape = mxRhombus.prototype.paintVertexShape;
    mxRhombus.prototype.getLabelBounds = function (rect) {
      if (this.style.double == 1) {
        const margin = (Math.max(2, this.strokewidth + 1) * 2 + parseFloat(
          this.style[mxConstants.STYLE_MARGIN] || 0,
        )) * this.scale;

        return new mxRectangle(rect.x + margin, rect.y + margin,
          rect.width - 2 * margin, rect.height - 2 * margin);
      }

      return rect;
    };
    mxRhombus.prototype.paintVertexShape = function (c, x, y, w, h) {
      mxRhombusPaintVertexShape.apply(this, arguments);

      if (!this.outline && this.style.double == 1) {
        const margin = Math.max(2, this.strokewidth + 1) * 2 +
          parseFloat(this.style[mxConstants.STYLE_MARGIN] || 0);
        x += margin;
        y += margin;
        w -= 2 * margin;
        h -= 2 * margin;

        if (w > 0 && h > 0) {
          c.setShadow(false);

          // Workaround for closure compiler bug where the lines with x and y above
          // are removed if arguments is used as second argument in call below.
          mxRhombusPaintVertexShape.apply(this, [c, x, y, w, h]);
        }
      }
    };

    // CompositeShape
    function ExtendedShape() {
      mxRectangleShape.call(this);
    }
    mxUtils.extend(ExtendedShape, mxRectangleShape);
    ExtendedShape.prototype.isHtmlAllowed = function () {
      return false;
    };
    ExtendedShape.prototype.getLabelBounds = function (rect) {
      if (this.style.double == 1) {
        const margin = (Math.max(2, this.strokewidth + 1) + parseFloat(
          this.style[mxConstants.STYLE_MARGIN] || 0,
        )) * this.scale;

        return new mxRectangle(rect.x + margin, rect.y + margin,
          rect.width - 2 * margin, rect.height - 2 * margin);
      }

      return rect;
    };

    ExtendedShape.prototype.paintForeground = function (c, x, y, w, h) {
      if (this.style != null) {
        if (!this.outline && this.style.double == 1) {
          const margin = Math.max(2, this.strokewidth + 1) + parseFloat(this.style[mxConstants.STYLE_MARGIN] || 0);
          x += margin;
          y += margin;
          w -= 2 * margin;
          h -= 2 * margin;

          if (w > 0 && h > 0) {
            mxRectangleShape.prototype.paintBackground.apply(this, arguments);
          }
        }

        c.setDashed(false);

        // Draws the symbols defined in the style. The symbols are
        // numbered from 1...n. Possible postfixes are align,
        // verticalAlign, spacing, arcSpacing, width, height
        let counter = 0;
        let shape = null;

        do {
          shape = mxCellRenderer.defaultShapes[this.style[`symbol${ counter}`]];

          if (shape != null) {
            const align = this.style[`symbol${counter }Align`];
            const valign = this.style[`symbol${counter}VerticalAlign`];
            const width = this.style[`symbol${counter}Width`];
            const height = this.style[`symbol${ counter}Height`];
            let spacing = this.style[`symbol${ counter }Spacing`] || 0;
            let vspacing = this.style[`symbol${ counter }VSpacing`] || spacing;
            const arcspacing = this.style[`symbol${counter}ArcSpacing`];

            if (arcspacing != null) {
              const arcSize = this.getArcSize(w + this.strokewidth, h + this.strokewidth) * arcspacing;
              spacing += arcSize;
              vspacing += arcSize;
            }

            let x2 = x;
            let y2 = y;

            if (align == mxConstants.ALIGN_CENTER) {
              x2 += (w - width) / 2;
            } else if (align == mxConstants.ALIGN_RIGHT) {
              x2 += w - width - spacing;
            } else {
              x2 += spacing;
            }

            if (valign == mxConstants.ALIGN_MIDDLE) {
              y2 += (h - height) / 2;
            } else if (valign == mxConstants.ALIGN_BOTTOM) {
              y2 += h - height - vspacing;
            } else {
              y2 += vspacing;
            }

            c.save();

            // Small hack to pass style along into subshape
            const tmp = new shape();
            // TODO: Clone style and override settings (eg. strokewidth)
            tmp.style = this.style;
            shape.prototype.paintVertexShape.call(tmp, c, x2, y2, width, height);
            c.restore();
          }

          counter++;
        }
        while (shape != null);
      }

      // Paints glass effect
      mxRectangleShape.prototype.paintForeground.apply(this, arguments);
    };

    mxCellRenderer.registerShape('ext', ExtendedShape);

    // Tape Shape, supports size style
    function MessageShape() {
      mxCylinder.call(this);
    }
    mxUtils.extend(MessageShape, mxCylinder);
    MessageShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
      if (isForeground) {
        path.moveTo(0, 0);
        path.lineTo(w / 2, h / 2);
        path.lineTo(w, 0);
        path.end();
      } else {
        path.moveTo(0, 0);
        path.lineTo(w, 0);
        path.lineTo(w, h);
        path.lineTo(0, h);
        path.close();
      }
    };

    mxCellRenderer.registerShape('message', MessageShape);

    // UML Actor Shape
    function UmlActorShape() {
      mxShape.call(this);
    }
    mxUtils.extend(UmlActorShape, mxShape);
    UmlActorShape.prototype.paintBackground = function (c, x, y, w, h) {
      c.translate(x, y);

      // Head
      c.ellipse(w / 4, 0, w / 2, h / 4);
      c.fillAndStroke();

      c.begin();
      c.moveTo(w / 2, h / 4);
      c.lineTo(w / 2, 2 * h / 3);

      // Arms
      c.moveTo(w / 2, h / 3);
      c.lineTo(0, h / 3);
      c.moveTo(w / 2, h / 3);
      c.lineTo(w, h / 3);

      // Legs
      c.moveTo(w / 2, 2 * h / 3);
      c.lineTo(0, h);
      c.moveTo(w / 2, 2 * h / 3);
      c.lineTo(w, h);
      c.end();

      c.stroke();
    };

    // Replaces existing actor shape
    mxCellRenderer.registerShape('umlActor', UmlActorShape);

    // UML Boundary Shape
    function UmlBoundaryShape() {
      mxShape.call(this);
    }
    mxUtils.extend(UmlBoundaryShape, mxShape);
    UmlBoundaryShape.prototype.getLabelMargins = function (rect) {
      return new mxRectangle(rect.width / 6, 0, 0, 0);
    };
    UmlBoundaryShape.prototype.paintBackground = function (c, x, y, w, h) {
      c.translate(x, y);

      // Base line
      c.begin();
      c.moveTo(0, h / 4);
      c.lineTo(0, h * 3 / 4);
      c.end();
      c.stroke();

      // Horizontal line
      c.begin();
      c.moveTo(0, h / 2);
      c.lineTo(w / 6, h / 2);
      c.end();
      c.stroke();

      // Circle
      c.ellipse(w / 6, 0, w * 5 / 6, h);
      c.fillAndStroke();
    };

    // Replaces existing actor shape
    mxCellRenderer.registerShape('umlBoundary', UmlBoundaryShape);

    // UML Entity Shape
    function UmlEntityShape() {
      mxEllipse.call(this);
    }
    mxUtils.extend(UmlEntityShape, mxEllipse);
    UmlEntityShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      mxEllipse.prototype.paintVertexShape.apply(this, arguments);

      c.begin();
      c.moveTo(x + w / 8, y + h);
      c.lineTo(x + w * 7 / 8, y + h);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('umlEntity', UmlEntityShape);

    // UML Destroy Shape
    function UmlDestroyShape() {
      mxShape.call(this);
    }
    mxUtils.extend(UmlDestroyShape, mxShape);
    UmlDestroyShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      c.translate(x, y);

      c.begin();
      c.moveTo(w, 0);
      c.lineTo(0, h);
      c.moveTo(0, 0);
      c.lineTo(w, h);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('umlDestroy', UmlDestroyShape);

    // UML Control Shape
    function UmlControlShape() {
      mxShape.call(this);
    }
    mxUtils.extend(UmlControlShape, mxShape);
    UmlControlShape.prototype.getLabelBounds = function (rect) {
      return new mxRectangle(rect.x, rect.y + rect.height / 8, rect.width, rect.height * 7 / 8);
    };
    UmlControlShape.prototype.paintBackground = function (c, x, y, w, h) {
      c.translate(x, y);

      // Upper line
      c.begin();
      c.moveTo(w * 3 / 8, h / 8 * 1.1);
      c.lineTo(w * 5 / 8, 0);
      c.end();
      c.stroke();

      // Circle
      c.ellipse(0, h / 8, w, h * 7 / 8);
      c.fillAndStroke();
    };
    UmlControlShape.prototype.paintForeground = function (c, x, y, w, h) {
      // Lower line
      c.begin();
      c.moveTo(w * 3 / 8, h / 8 * 1.1);
      c.lineTo(w * 5 / 8, h / 4);
      c.end();
      c.stroke();
    };

    // Replaces existing actor shape
    mxCellRenderer.registerShape('umlControl', UmlControlShape);

    // UML Lifeline Shape
    function UmlLifeline() {
      mxRectangleShape.call(this);
    }
    mxUtils.extend(UmlLifeline, mxRectangleShape);
    UmlLifeline.prototype.size = 40;
    UmlLifeline.prototype.isHtmlAllowed = function () {
      return false;
    };
    UmlLifeline.prototype.getLabelBounds = function (rect) {
      const size = Math.max(0, Math.min(rect.height, parseFloat(
        mxUtils.getValue(this.style, 'size', this.size),
      ) * this.scale));

      return new mxRectangle(rect.x, rect.y, rect.width, size);
    };
    UmlLifeline.prototype.paintBackground = function (c, x, y, w, h) {
      const size = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const participant = mxUtils.getValue(this.style, 'participant');

      if (participant == null || this.state == null) {
        mxRectangleShape.prototype.paintBackground.call(this, c, x, y, w, size);
      } else {
        const ctor = this.state.view.graph.cellRenderer.getShape(participant);

        if (ctor != null && ctor != UmlLifeline) {
          const shape = new ctor();
          shape.apply(this.state);
          c.save();
          shape.paintVertexShape(c, x, y, w, size);
          c.restore();
        }
      }

      if (size < h) {
        c.setDashed(true);
        c.begin();
        c.moveTo(x + w / 2, y + size);
        c.lineTo(x + w / 2, y + h);
        c.end();
        c.stroke();
      }
    };
    UmlLifeline.prototype.paintForeground = function (c, x, y, w, h) {
      const size = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      mxRectangleShape.prototype.paintForeground.call(this, c, x, y, w, Math.min(h, size));
    };

    mxCellRenderer.registerShape('umlLifeline', UmlLifeline);

    // UML Frame Shape
    function UmlFrame() {
      mxShape.call(this);
    }
    mxUtils.extend(UmlFrame, mxShape);
    UmlFrame.prototype.width = 60;
    UmlFrame.prototype.height = 30;
    UmlFrame.prototype.corner = 10;
    UmlFrame.prototype.getLabelMargins = function (rect) {
      return new mxRectangle(0, 0,
        rect.width - (parseFloat(mxUtils.getValue(this.style, 'width', this.width) * this.scale)),
        rect.height - (parseFloat(mxUtils.getValue(this.style, 'height', this.height) * this.scale)));
    };
    UmlFrame.prototype.paintBackground = function (c, x, y, w, h) {
      const co = this.corner;
      const w0 = Math.min(w, Math.max(co, parseFloat(mxUtils.getValue(this.style, 'width', this.width))));
      const h0 = Math.min(h, Math.max(co * 1.5, parseFloat(mxUtils.getValue(this.style, 'height', this.height))));
      const bg = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, mxConstants.NONE);

      if (bg != mxConstants.NONE) {
        c.setFillColor(bg);
        c.rect(x, y, w, h);
        c.fill();
      }

      if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) {
        const b = this.getGradientBounds(c, x, y, w, h);
        c.setGradient(this.fill, this.gradient, x, y, w, h, this.gradientDirection);
      } else {
        c.setFillColor(this.fill);
      }

      c.begin();
      c.moveTo(x, y);
      c.lineTo(x + w0, y);
      c.lineTo(x + w0, y + Math.max(0, h0 - co * 1.5));
      c.lineTo(x + Math.max(0, w0 - co), y + h0);
      c.lineTo(x, y + h0);
      c.close();
      c.fillAndStroke();

      c.begin();
      c.moveTo(x + w0, y);
      c.lineTo(x + w, y);
      c.lineTo(x + w, y + h);
      c.lineTo(x, y + h);
      c.lineTo(x, y + h0);
      c.stroke();
    };

    mxCellRenderer.registerShape('umlFrame', UmlFrame);

    mxPerimeter.LifelinePerimeter = function (bounds, vertex, next, orthogonal) {
      let size = UmlLifeline.prototype.size;

      if (vertex != null) {
        size = mxUtils.getValue(vertex.style, 'size', size) * vertex.view.scale;
      }

      let sw = (parseFloat(vertex.style[mxConstants.STYLE_STROKEWIDTH] || 1) * vertex.view.scale / 2) - 1;

      if (next.x < bounds.getCenterX()) {
        sw += 1;
        sw *= -1;
      }

      return new mxPoint(bounds.getCenterX() + sw, Math.min(bounds.y + bounds.height,
        Math.max(bounds.y + size, next.y)));
    };

    mxStyleRegistry.putValue('lifelinePerimeter', mxPerimeter.LifelinePerimeter);

    mxPerimeter.OrthogonalPerimeter = function (bounds, vertex, next, orthogonal) {
      orthogonal = true;

      return mxPerimeter.RectanglePerimeter.apply(this, arguments);
    };

    mxStyleRegistry.putValue('orthogonalPerimeter', mxPerimeter.OrthogonalPerimeter);

    mxPerimeter.BackbonePerimeter = function (bounds, vertex, next, orthogonal) {
      let sw = (parseFloat(vertex.style[mxConstants.STYLE_STROKEWIDTH] || 1) * vertex.view.scale / 2) - 1;

      if (vertex.style.backboneSize != null) {
        sw += (parseFloat(vertex.style.backboneSize) * vertex.view.scale / 2) - 1;
      }

      if (vertex.style[mxConstants.STYLE_DIRECTION] == 'south' ||
        vertex.style[mxConstants.STYLE_DIRECTION] == 'north') {
        if (next.x < bounds.getCenterX()) {
          sw += 1;
          sw *= -1;
        }

        return new mxPoint(bounds.getCenterX() + sw, Math.min(bounds.y + bounds.height,
          Math.max(bounds.y, next.y)));
      }

      if (next.y < bounds.getCenterY()) {
        sw += 1;
        sw *= -1;
      }

      return new mxPoint(Math.min(bounds.x + bounds.width, Math.max(bounds.x, next.x)),
        bounds.getCenterY() + sw);
    };

    mxStyleRegistry.putValue('backbonePerimeter', mxPerimeter.BackbonePerimeter);

    // Callout Perimeter
    mxPerimeter.CalloutPerimeter = function (bounds, vertex, next, orthogonal) {
      return mxPerimeter.RectanglePerimeter(mxUtils.getDirectedBounds(bounds, new mxRectangle(0, 0, 0,
          Math.max(0, Math.min(bounds.height, parseFloat(mxUtils.getValue(vertex.style, 'size',
            CalloutShape.prototype.size)) * vertex.view.scale))),
        vertex.style), vertex, next, orthogonal);
    };

    mxStyleRegistry.putValue('calloutPerimeter', mxPerimeter.CalloutPerimeter);

    // Parallelogram Perimeter
    mxPerimeter.ParallelogramPerimeter = function (bounds, vertex, next, orthogonal) {
      let size = ParallelogramShape.prototype.size;

      if (vertex != null) {
        size = mxUtils.getValue(vertex.style, 'size', size);
      }

      const x = bounds.x;
      const y = bounds.y;
      const w = bounds.width;
      const h = bounds.height;

      const direction = (vertex != null) ? mxUtils.getValue(
        vertex.style, mxConstants.STYLE_DIRECTION,
        mxConstants.DIRECTION_EAST,
      ) : mxConstants.DIRECTION_EAST;
      const vertical = direction == mxConstants.DIRECTION_NORTH ||
        direction == mxConstants.DIRECTION_SOUTH;
      let points;

      if (vertical) {
        const dy = h * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x, y), new mxPoint(x + w, y + dy),
          new mxPoint(x + w, y + h), new mxPoint(x, y + h - dy), new mxPoint(x, y)
        ];
      } else {
        const dx = w * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x + dx, y), new mxPoint(x + w, y),
          new mxPoint(x + w - dx, y + h), new mxPoint(x, y + h), new mxPoint(x + dx, y)
        ];
      }

      const cx = bounds.getCenterX();
      const cy = bounds.getCenterY();

      const p1 = new mxPoint(cx, cy);

      if (orthogonal) {
        if (next.x < x || next.x > x + w) {
          p1.y = next.y;
        } else {
          p1.x = next.x;
        }
      }

      return mxUtils.getPerimeterPoint(points, p1, next);
    };

    mxStyleRegistry.putValue('parallelogramPerimeter', mxPerimeter.ParallelogramPerimeter);

    // Trapezoid Perimeter
    mxPerimeter.TrapezoidPerimeter = function (bounds, vertex, next, orthogonal) {
      let size = TrapezoidShape.prototype.size;

      if (vertex != null) {
        size = mxUtils.getValue(vertex.style, 'size', size);
      }

      const x = bounds.x;
      const y = bounds.y;
      const w = bounds.width;
      const h = bounds.height;

      const direction = (vertex != null) ? mxUtils.getValue(
        vertex.style, mxConstants.STYLE_DIRECTION,
        mxConstants.DIRECTION_EAST,
      ) : mxConstants.DIRECTION_EAST;
      let points;

      if (direction == mxConstants.DIRECTION_EAST) {
        var dx = w * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x + dx, y), new mxPoint(x + w - dx, y),
          new mxPoint(x + w, y + h), new mxPoint(x, y + h), new mxPoint(x + dx, y)
        ];
      } else if (direction == mxConstants.DIRECTION_WEST) {
        var dx = w * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x, y), new mxPoint(x + w, y),
          new mxPoint(x + w - dx, y + h), new mxPoint(x + dx, y + h), new mxPoint(x, y)
        ];
      } else if (direction == mxConstants.DIRECTION_NORTH) {
        var dy = h * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x, y + dy), new mxPoint(x + w, y),
          new mxPoint(x + w, y + h), new mxPoint(x, y + h - dy), new mxPoint(x, y + dy)
        ];
      } else {
        var dy = h * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x, y), new mxPoint(x + w, y + dy),
          new mxPoint(x + w, y + h - dy), new mxPoint(x, y + h), new mxPoint(x, y)
        ];
      }

      const cx = bounds.getCenterX();
      const cy = bounds.getCenterY();

      const p1 = new mxPoint(cx, cy);

      if (orthogonal) {
        if (next.x < x || next.x > x + w) {
          p1.y = next.y;
        } else {
          p1.x = next.x;
        }
      }

      return mxUtils.getPerimeterPoint(points, p1, next);
    };

    mxStyleRegistry.putValue('trapezoidPerimeter', mxPerimeter.TrapezoidPerimeter);

    // Step Perimeter
    mxPerimeter.StepPerimeter = function (bounds, vertex, next, orthogonal) {
      const fixed = mxUtils.getValue(vertex.style, 'fixedSize', '0') != '0';
      let size = (fixed) ? StepShape.prototype.fixedSize : StepShape.prototype.size;

      if (vertex != null) {
        size = mxUtils.getValue(vertex.style, 'size', size);
      }

      const x = bounds.x;
      const y = bounds.y;
      const w = bounds.width;
      const h = bounds.height;

      const cx = bounds.getCenterX();
      const cy = bounds.getCenterY();

      const direction = (vertex != null) ? mxUtils.getValue(
        vertex.style, mxConstants.STYLE_DIRECTION,
        mxConstants.DIRECTION_EAST,
      ) : mxConstants.DIRECTION_EAST;
      let points;

      if (direction == mxConstants.DIRECTION_EAST) {
        var dx = (fixed) ? Math.max(0, Math.min(w, size)) : w * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x, y), new mxPoint(x + w - dx, y), new mxPoint(x + w, cy),
          new mxPoint(x + w - dx, y + h), new mxPoint(x, y + h),
          new mxPoint(x + dx, cy), new mxPoint(x, y)
        ];
      } else if (direction == mxConstants.DIRECTION_WEST) {
        var dx = (fixed) ? Math.max(0, Math.min(w, size)) : w * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x + dx, y), new mxPoint(x + w, y), new mxPoint(x + w - dx, cy),
          new mxPoint(x + w, y + h), new mxPoint(x + dx, y + h),
          new mxPoint(x, cy), new mxPoint(x + dx, y)
        ];
      } else if (direction == mxConstants.DIRECTION_NORTH) {
        var dy = (fixed) ? Math.max(0, Math.min(h, size)) : h * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x, y + dy), new mxPoint(cx, y), new mxPoint(x + w, y + dy),
          new mxPoint(x + w, y + h), new mxPoint(cx, y + h - dy),
          new mxPoint(x, y + h), new mxPoint(x, y + dy)
        ];
      } else {
        var dy = (fixed) ? Math.max(0, Math.min(h, size)) : h * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x, y), new mxPoint(cx, y + dy), new mxPoint(x + w, y),
          new mxPoint(x + w, y + h - dy), new mxPoint(cx, y + h),
          new mxPoint(x, y + h - dy), new mxPoint(x, y)
        ];
      }

      const p1 = new mxPoint(cx, cy);

      if (orthogonal) {
        if (next.x < x || next.x > x + w) {
          p1.y = next.y;
        } else {
          p1.x = next.x;
        }
      }

      return mxUtils.getPerimeterPoint(points, p1, next);
    };

    mxStyleRegistry.putValue('stepPerimeter', mxPerimeter.StepPerimeter);

    // Hexagon Perimeter 2 (keep existing one)
    mxPerimeter.HexagonPerimeter2 = function (bounds, vertex, next, orthogonal) {
      let size = HexagonShape.prototype.size;

      if (vertex != null) {
        size = mxUtils.getValue(vertex.style, 'size', size);
      }

      const x = bounds.x;
      const y = bounds.y;
      const w = bounds.width;
      const h = bounds.height;

      const cx = bounds.getCenterX();
      const cy = bounds.getCenterY();

      const direction = (vertex != null) ? mxUtils.getValue(
        vertex.style, mxConstants.STYLE_DIRECTION,
        mxConstants.DIRECTION_EAST,
      ) : mxConstants.DIRECTION_EAST;
      const vertical = direction == mxConstants.DIRECTION_NORTH ||
        direction == mxConstants.DIRECTION_SOUTH;
      let points;

      if (vertical) {
        const dy = h * Math.max(0, Math.min(1, size));
        points = [new mxPoint(cx, y), new mxPoint(x + w, y + dy), new mxPoint(x + w, y + h - dy),
          new mxPoint(cx, y + h), new mxPoint(x, y + h - dy),
          new mxPoint(x, y + dy), new mxPoint(cx, y)
        ];
      } else {
        const dx = w * Math.max(0, Math.min(1, size));
        points = [new mxPoint(x + dx, y), new mxPoint(x + w - dx, y), new mxPoint(x + w, cy),
          new mxPoint(x + w - dx, y + h), new mxPoint(x + dx, y + h),
          new mxPoint(x, cy), new mxPoint(x + dx, y)
        ];
      }

      const p1 = new mxPoint(cx, cy);

      if (orthogonal) {
        if (next.x < x || next.x > x + w) {
          p1.y = next.y;
        } else {
          p1.x = next.x;
        }
      }

      return mxUtils.getPerimeterPoint(points, p1, next);
    };

    mxStyleRegistry.putValue('hexagonPerimeter2', mxPerimeter.HexagonPerimeter2);

    // Provided Interface Shape (aka Lollipop)
    function LollipopShape() {
      mxShape.call(this);
    }
    mxUtils.extend(LollipopShape, mxShape);
    LollipopShape.prototype.size = 10;
    LollipopShape.prototype.paintBackground = function (c, x, y, w, h) {
      const sz = parseFloat(mxUtils.getValue(this.style, 'size', this.size));
      c.translate(x, y);

      c.ellipse((w - sz) / 2, 0, sz, sz);
      c.fillAndStroke();

      c.begin();
      c.moveTo(w / 2, sz);
      c.lineTo(w / 2, h);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('lollipop', LollipopShape);

    // Required Interface Shape
    function RequiresShape() {
      mxShape.call(this);
    }
    mxUtils.extend(RequiresShape, mxShape);
    RequiresShape.prototype.size = 10;
    RequiresShape.prototype.inset = 2;
    RequiresShape.prototype.paintBackground = function (c, x, y, w, h) {
      const sz = parseFloat(mxUtils.getValue(this.style, 'size', this.size));
      const inset = parseFloat(mxUtils.getValue(this.style, 'inset', this.inset)) + this.strokewidth;
      c.translate(x, y);

      c.begin();
      c.moveTo(w / 2, sz + inset);
      c.lineTo(w / 2, h);
      c.end();
      c.stroke();

      c.begin();
      c.moveTo((w - sz) / 2 - inset, sz / 2);
      c.quadTo((w - sz) / 2 - inset, sz + inset, w / 2, sz + inset);
      c.quadTo((w + sz) / 2 + inset, sz + inset, (w + sz) / 2 + inset, sz / 2);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('requires', RequiresShape);

    // Required Interface Shape
    function RequiredInterfaceShape() {
      mxShape.call(this);
    }
    mxUtils.extend(RequiredInterfaceShape, mxShape);

    RequiredInterfaceShape.prototype.paintBackground = function (c, x, y, w, h) {
      c.translate(x, y);

      c.begin();
      c.moveTo(0, 0);
      c.quadTo(w, 0, w, h / 2);
      c.quadTo(w, h, 0, h);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('requiredInterface', RequiredInterfaceShape);

    // Provided and Required Interface Shape
    function ProvidedRequiredInterfaceShape() {
      mxShape.call(this);
    }
    mxUtils.extend(ProvidedRequiredInterfaceShape, mxShape);
    ProvidedRequiredInterfaceShape.prototype.inset = 2;
    ProvidedRequiredInterfaceShape.prototype.paintBackground = function (c, x, y, w, h) {
      const inset = parseFloat(mxUtils.getValue(this.style, 'inset', this.inset)) + this.strokewidth;
      c.translate(x, y);

      c.ellipse(0, inset, w - 2 * inset, h - 2 * inset);
      c.fillAndStroke();

      c.begin();
      c.moveTo(w / 2, 0);
      c.quadTo(w, 0, w, h / 2);
      c.quadTo(w, h, w / 2, h);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('providedRequiredInterface', ProvidedRequiredInterfaceShape);

    // Component shape
    function ComponentShape() {
      mxCylinder.call(this);
    }
    mxUtils.extend(ComponentShape, mxCylinder);
    ComponentShape.prototype.jettyWidth = 32;
    ComponentShape.prototype.jettyHeight = 12;
    ComponentShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
      const dx = parseFloat(mxUtils.getValue(this.style, 'jettyWidth', this.jettyWidth));
      const dy = parseFloat(mxUtils.getValue(this.style, 'jettyHeight', this.jettyHeight));
      const x0 = dx / 2;
      const x1 = x0 + dx / 2;
      const y0 = 0.3 * h - dy / 2;
      const y1 = 0.7 * h - dy / 2;

      if (isForeground) {
        path.moveTo(x0, y0);
        path.lineTo(x1, y0);
        path.lineTo(x1, y0 + dy);
        path.lineTo(x0, y0 + dy);
        path.moveTo(x0, y1);
        path.lineTo(x1, y1);
        path.lineTo(x1, y1 + dy);
        path.lineTo(x0, y1 + dy);
        path.end();
      } else {
        path.moveTo(x0, 0);
        path.lineTo(w, 0);
        path.lineTo(w, h);
        path.lineTo(x0, h);
        path.lineTo(x0, y1 + dy);
        path.lineTo(0, y1 + dy);
        path.lineTo(0, y1);
        path.lineTo(x0, y1);
        path.lineTo(x0, y0 + dy);
        path.lineTo(0, y0 + dy);
        path.lineTo(0, y0);
        path.lineTo(x0, y0);
        path.close();
        path.end();
      }
    };

    mxCellRenderer.registerShape('component', ComponentShape);

    // State Shapes derives from double ellipse
    function StateShape() {
      mxDoubleEllipse.call(this);
    }
    mxUtils.extend(StateShape, mxDoubleEllipse);
    StateShape.prototype.outerStroke = true;
    StateShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      const inset = Math.min(4, Math.min(w / 5, h / 5));

      if (w > 0 && h > 0) {
        c.ellipse(x + inset, y + inset, w - 2 * inset, h - 2 * inset);
        c.fillAndStroke();
      }

      c.setShadow(false);

      if (this.outerStroke) {
        c.ellipse(x, y, w, h);
        c.stroke();
      }
    };

    mxCellRenderer.registerShape('endState', StateShape);

    function StartStateShape() {
      StateShape.call(this);
    }
    mxUtils.extend(StartStateShape, StateShape);
    StartStateShape.prototype.outerStroke = false;

    mxCellRenderer.registerShape('startState', StartStateShape);

    // Link shape
    function LinkShape() {
      mxArrowConnector.call(this);
      this.spacing = 0;
    }
    mxUtils.extend(LinkShape, mxArrowConnector);
    LinkShape.prototype.defaultWidth = 4;

    LinkShape.prototype.isOpenEnded = function () {
      return true;
    };

    LinkShape.prototype.getEdgeWidth = function () {
      return mxUtils.getNumber(this.style, 'width', this.defaultWidth) + Math.max(0, this.strokewidth - 1);
    };

    LinkShape.prototype.isArrowRounded = function () {
      return this.isRounded;
    };

    // Registers the link shape
    mxCellRenderer.registerShape('link', LinkShape);

    // Generic arrow
    function FlexArrowShape() {
      mxArrowConnector.call(this);
      this.spacing = 0;
    }
    mxUtils.extend(FlexArrowShape, mxArrowConnector);
    FlexArrowShape.prototype.defaultWidth = 10;
    FlexArrowShape.prototype.defaultArrowWidth = 20;

    FlexArrowShape.prototype.getStartArrowWidth = function () {
      return this.getEdgeWidth() + mxUtils.getNumber(this.style, 'startWidth', this.defaultArrowWidth);
    };

    FlexArrowShape.prototype.getEndArrowWidth = function () {
      return this.getEdgeWidth() + mxUtils.getNumber(this.style, 'endWidth', this.defaultArrowWidth);
    };

    FlexArrowShape.prototype.getEdgeWidth = function () {
      return mxUtils.getNumber(this.style, 'width', this.defaultWidth) + Math.max(0, this.strokewidth - 1);
    };

    // Registers the link shape
    mxCellRenderer.registerShape('flexArrow', FlexArrowShape);

    // Manual Input shape
    function ManualInputShape() {
      mxActor.call(this);
    }
    mxUtils.extend(ManualInputShape, mxActor);
    ManualInputShape.prototype.size = 30;
    ManualInputShape.prototype.isRoundable = function () {
      return true;
    };
    ManualInputShape.prototype.redrawPath = function (c, x, y, w, h) {
      const s = Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(0, h), new mxPoint(0, s), new mxPoint(w, 0), new mxPoint(w, h)],
        this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('manualInput', ManualInputShape);

    // Internal storage
    function InternalStorageShape() {
      mxRectangleShape.call(this);
    }
    mxUtils.extend(InternalStorageShape, mxRectangleShape);
    InternalStorageShape.prototype.dx = 20;
    InternalStorageShape.prototype.dy = 20;
    InternalStorageShape.prototype.isHtmlAllowed = function () {
      return false;
    };
    InternalStorageShape.prototype.paintForeground = function (c, x, y, w, h) {
      mxRectangleShape.prototype.paintForeground.apply(this, arguments);
      let inset = 0;

      if (this.isRounded) {
        const f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
          mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
        inset = Math.max(inset, Math.min(w * f, h * f));
      }

      const dx = Math.max(inset, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'dx', this.dx))));
      const dy = Math.max(inset, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'dy', this.dy))));

      c.begin();
      c.moveTo(x, y + dy);
      c.lineTo(x + w, y + dy);
      c.end();
      c.stroke();

      c.begin();
      c.moveTo(x + dx, y);
      c.lineTo(x + dx, y + h);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('internalStorage', InternalStorageShape);

    // Internal storage
    function CornerShape() {
      mxActor.call(this);
    }
    mxUtils.extend(CornerShape, mxActor);
    CornerShape.prototype.dx = 20;
    CornerShape.prototype.dy = 20;

    // Corner
    CornerShape.prototype.redrawPath = function (c, x, y, w, h) {
      const dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'dx', this.dx))));
      const dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'dy', this.dy))));

      const s = Math.min(w / 2, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(0, 0), new mxPoint(w, 0), new mxPoint(w, dy), new mxPoint(dx, dy),
        new mxPoint(dx, h), new mxPoint(0, h)
      ], this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('corner', CornerShape);

    // Crossbar shape
    function CrossbarShape() {
      mxActor.call(this);
    }
    mxUtils.extend(CrossbarShape, mxActor);

    CrossbarShape.prototype.redrawPath = function (c, x, y, w, h) {
      c.moveTo(0, 0);
      c.lineTo(0, h);
      c.end();

      c.moveTo(w, 0);
      c.lineTo(w, h);
      c.end();

      c.moveTo(0, h / 2);
      c.lineTo(w, h / 2);
      c.end();
    };

    mxCellRenderer.registerShape('crossbar', CrossbarShape);

    // Internal storage
    function TeeShape() {
      mxActor.call(this);
    }
    mxUtils.extend(TeeShape, mxActor);
    TeeShape.prototype.dx = 20;
    TeeShape.prototype.dy = 20;

    // Corner
    TeeShape.prototype.redrawPath = function (c, x, y, w, h) {
      const dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'dx', this.dx))));
      const dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'dy', this.dy))));
      const w2 = Math.abs(w - dx) / 2;

      const s = Math.min(w / 2, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(0, 0), new mxPoint(w, 0), new mxPoint(w, dy), new mxPoint((w + dx) / 2, dy),
        new mxPoint((w + dx) / 2, h), new mxPoint((w - dx) / 2, h), new mxPoint((w - dx) / 2, dy),
        new mxPoint(0, dy)
      ], this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('tee', TeeShape);

    // Arrow
    function SingleArrowShape() {
      mxActor.call(this);
    }
    mxUtils.extend(SingleArrowShape, mxActor);
    SingleArrowShape.prototype.arrowWidth = 0.3;
    SingleArrowShape.prototype.arrowSize = 0.2;
    SingleArrowShape.prototype.redrawPath = function (c, x, y, w, h) {
      const aw = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowWidth', this.arrowWidth))));
      const as = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowSize', this.arrowSize))));
      const at = (h - aw) / 2;
      const ab = at + aw;

      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(0, at), new mxPoint(w - as, at), new mxPoint(w - as, 0), new mxPoint(w, h / 2),
          new mxPoint(w - as, h), new mxPoint(w - as, ab), new mxPoint(0, ab)
        ],
        this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('singleArrow', SingleArrowShape);

    // Arrow
    function DoubleArrowShape() {
      mxActor.call(this);
    }
    mxUtils.extend(DoubleArrowShape, mxActor);
    DoubleArrowShape.prototype.redrawPath = function (c, x, y, w, h) {
      const aw = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowWidth', SingleArrowShape.prototype.arrowWidth))));
      const as = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowSize', SingleArrowShape.prototype.arrowSize))));
      const at = (h - aw) / 2;
      const ab = at + aw;

      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(0, h / 2), new mxPoint(as, 0), new mxPoint(as, at), new mxPoint(w - as, at),
          new mxPoint(w - as, 0), new mxPoint(w, h / 2), new mxPoint(w - as, h),
          new mxPoint(w - as, ab), new mxPoint(as, ab), new mxPoint(as, h)
        ],
        this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('doubleArrow', DoubleArrowShape);

    // Data storage
    function DataStorageShape() {
      mxActor.call(this);
    }
    mxUtils.extend(DataStorageShape, mxActor);
    DataStorageShape.prototype.size = 0.1;
    DataStorageShape.prototype.redrawPath = function (c, x, y, w, h) {
      const s = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));

      c.moveTo(s, 0);
      c.lineTo(w, 0);
      c.quadTo(w - s * 2, h / 2, w, h);
      c.lineTo(s, h);
      c.quadTo(s - s * 2, h / 2, s, 0);
      c.close();
      c.end();
    };

    mxCellRenderer.registerShape('dataStorage', DataStorageShape);

    // Or
    function OrShape() {
      mxActor.call(this);
    }
    mxUtils.extend(OrShape, mxActor);
    OrShape.prototype.redrawPath = function (c, x, y, w, h) {
      c.moveTo(0, 0);
      c.quadTo(w, 0, w, h / 2);
      c.quadTo(w, h, 0, h);
      c.close();
      c.end();
    };

    mxCellRenderer.registerShape('or', OrShape);

    // Xor
    function XorShape() {
      mxActor.call(this);
    }
    mxUtils.extend(XorShape, mxActor);
    XorShape.prototype.redrawPath = function (c, x, y, w, h) {
      c.moveTo(0, 0);
      c.quadTo(w, 0, w, h / 2);
      c.quadTo(w, h, 0, h);
      c.quadTo(w / 2, h / 2, 0, 0);
      c.close();
      c.end();
    };

    mxCellRenderer.registerShape('xor', XorShape);

    // Loop limit
    function LoopLimitShape() {
      mxActor.call(this);
    }
    mxUtils.extend(LoopLimitShape, mxActor);
    LoopLimitShape.prototype.size = 20;
    LoopLimitShape.prototype.isRoundable = function () {
      return true;
    };
    LoopLimitShape.prototype.redrawPath = function (c, x, y, w, h) {
      const s = Math.min(w / 2, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(s, 0), new mxPoint(w - s, 0), new mxPoint(w, s * 0.8), new mxPoint(w, h),
        new mxPoint(0, h), new mxPoint(0, s * 0.8)
      ], this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('loopLimit', LoopLimitShape);

    // Off page connector
    function OffPageConnectorShape() {
      mxActor.call(this);
    }
    mxUtils.extend(OffPageConnectorShape, mxActor);
    OffPageConnectorShape.prototype.size = 3 / 8;
    OffPageConnectorShape.prototype.isRoundable = function () {
      return true;
    };
    OffPageConnectorShape.prototype.redrawPath = function (c, x, y, w, h) {
      const s = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      this.addPoints(c, [new mxPoint(0, 0), new mxPoint(w, 0), new mxPoint(w, h - s), new mxPoint(w / 2, h),
        new mxPoint(0, h - s)
      ], this.isRounded, arcSize, true);
      c.end();
    };

    mxCellRenderer.registerShape('offPageConnector', OffPageConnectorShape);

    // Internal storage
    function TapeDataShape() {
      mxEllipse.call(this);
    }
    mxUtils.extend(TapeDataShape, mxEllipse);
    TapeDataShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      mxEllipse.prototype.paintVertexShape.apply(this, arguments);

      c.begin();
      c.moveTo(x + w / 2, y + h);
      c.lineTo(x + w, y + h);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('tapeData', TapeDataShape);

    // OrEllipseShape
    function OrEllipseShape() {
      mxEllipse.call(this);
    }
    mxUtils.extend(OrEllipseShape, mxEllipse);
    OrEllipseShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      mxEllipse.prototype.paintVertexShape.apply(this, arguments);

      c.setShadow(false);
      c.begin();
      c.moveTo(x, y + h / 2);
      c.lineTo(x + w, y + h / 2);
      c.end();
      c.stroke();

      c.begin();
      c.moveTo(x + w / 2, y);
      c.lineTo(x + w / 2, y + h);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('orEllipse', OrEllipseShape);

    // SumEllipseShape
    function SumEllipseShape() {
      mxEllipse.call(this);
    }
    mxUtils.extend(SumEllipseShape, mxEllipse);
    SumEllipseShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      mxEllipse.prototype.paintVertexShape.apply(this, arguments);
      const s2 = 0.145;

      c.setShadow(false);
      c.begin();
      c.moveTo(x + w * s2, y + h * s2);
      c.lineTo(x + w * (1 - s2), y + h * (1 - s2));
      c.end();
      c.stroke();

      c.begin();
      c.moveTo(x + w * (1 - s2), y + h * s2);
      c.lineTo(x + w * s2, y + h * (1 - s2));
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('sumEllipse', SumEllipseShape);

    // SortShape
    function SortShape() {
      mxRhombus.call(this);
    }
    mxUtils.extend(SortShape, mxRhombus);
    SortShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      mxRhombus.prototype.paintVertexShape.apply(this, arguments);

      c.setShadow(false);
      c.begin();
      c.moveTo(x, y + h / 2);
      c.lineTo(x + w, y + h / 2);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('sortShape', SortShape);

    // CollateShape
    function CollateShape() {
      mxEllipse.call(this);
    }
    mxUtils.extend(CollateShape, mxEllipse);
    CollateShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      c.begin();
      c.moveTo(x, y);
      c.lineTo(x + w, y);
      c.lineTo(x + w / 2, y + h / 2);
      c.close();
      c.fillAndStroke();

      c.begin();
      c.moveTo(x, y + h);
      c.lineTo(x + w, y + h);
      c.lineTo(x + w / 2, y + h / 2);
      c.close();
      c.fillAndStroke();
    };

    mxCellRenderer.registerShape('collate', CollateShape);

    // DimensionShape
    function DimensionShape() {
      mxEllipse.call(this);
    }
    mxUtils.extend(DimensionShape, mxEllipse);
    DimensionShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      // Arrow size
      const al = 10;
      const cy = y + h - al / 2;

      c.begin();
      c.moveTo(x, y);
      c.lineTo(x, y + h);
      c.moveTo(x, cy);
      c.lineTo(x + al, cy - al / 2);
      c.moveTo(x, cy);
      c.lineTo(x + al, cy + al / 2);
      c.moveTo(x, cy);
      c.lineTo(x + w, cy);

      // Opposite side
      c.moveTo(x + w, y);
      c.lineTo(x + w, y + h);
      c.moveTo(x + w, cy);
      c.lineTo(x + w - al, cy - al / 2);
      c.moveTo(x + w, cy);
      c.lineTo(x + w - al, cy + al / 2);
      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('dimension', DimensionShape);

    // PartialRectangleShape
    function PartialRectangleShape() {
      mxEllipse.call(this);
    }
    mxUtils.extend(PartialRectangleShape, mxEllipse);
    PartialRectangleShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      if (!this.outline) {
        c.setStrokeColor(null);
      }

      mxRectangleShape.prototype.paintBackground.apply(this, arguments);

      if (this.style != null) {
        c.setStrokeColor(this.stroke);
        c.rect(x, y, w, h);
        c.fill();

        c.begin();
        c.moveTo(x, y);

        if (mxUtils.getValue(this.style, 'top', '1') == '1') {
          c.lineTo(x + w, y);
        } else {
          c.moveTo(x + w, y);
        }

        if (mxUtils.getValue(this.style, 'right', '1') == '1') {
          c.lineTo(x + w, y + h);
        } else {
          c.moveTo(x + w, y + h);
        }

        if (mxUtils.getValue(this.style, 'bottom', '1') == '1') {
          c.lineTo(x, y + h);
        } else {
          c.moveTo(x, y + h);
        }

        if (mxUtils.getValue(this.style, 'left', '1') == '1') {
          c.lineTo(x, y - this.strokewidth / 2);
        }

        c.end();
        c.stroke();
      }
    };

    mxCellRenderer.registerShape('partialRectangle', PartialRectangleShape);

    // LineEllipseShape
    function LineEllipseShape() {
      mxEllipse.call(this);
    }
    mxUtils.extend(LineEllipseShape, mxEllipse);
    LineEllipseShape.prototype.paintVertexShape = function (c, x, y, w, h) {
      mxEllipse.prototype.paintVertexShape.apply(this, arguments);

      c.setShadow(false);
      c.begin();

      if (mxUtils.getValue(this.style, 'line') == 'vertical') {
        c.moveTo(x + w / 2, y);
        c.lineTo(x + w / 2, y + h);
      } else {
        c.moveTo(x, y + h / 2);
        c.lineTo(x + w, y + h / 2);
      }

      c.end();
      c.stroke();
    };

    mxCellRenderer.registerShape('lineEllipse', LineEllipseShape);

    // Delay
    function DelayShape() {
      mxActor.call(this);
    }
    mxUtils.extend(DelayShape, mxActor);
    DelayShape.prototype.redrawPath = function (c, x, y, w, h) {
      const dx = Math.min(w, h / 2);
      c.moveTo(0, 0);
      c.lineTo(w - dx, 0);
      c.quadTo(w, 0, w, h / 2);
      c.quadTo(w, h, w - dx, h);
      c.lineTo(0, h);
      c.close();
      c.end();
    };

    mxCellRenderer.registerShape('delay', DelayShape);

    // Cross Shape
    function CrossShape() {
      mxActor.call(this);
    }
    mxUtils.extend(CrossShape, mxActor);
    CrossShape.prototype.size = 0.2;
    CrossShape.prototype.redrawPath = function (c, x, y, w, h) {
      const m = Math.min(h, w);
      const size = Math.max(0, Math.min(m, m * parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
      const t = (h - size) / 2;
      const b = t + size;
      const l = (w - size) / 2;
      const r = l + size;

      c.moveTo(0, t);
      c.lineTo(l, t);
      c.lineTo(l, 0);
      c.lineTo(r, 0);
      c.lineTo(r, t);
      c.lineTo(w, t);
      c.lineTo(w, b);
      c.lineTo(r, b);
      c.lineTo(r, h);
      c.lineTo(l, h);
      c.lineTo(l, b);
      c.lineTo(0, b);
      c.close();
      c.end();
    };

    mxCellRenderer.registerShape('cross', CrossShape);

    // Display
    function DisplayShape() {
      mxActor.call(this);
    }
    mxUtils.extend(DisplayShape, mxActor);
    DisplayShape.prototype.size = 0.25;
    DisplayShape.prototype.redrawPath = function (c, x, y, w, h) {
      const dx = Math.min(w, h / 2);
      const s = Math.min(w - dx, Math.max(0, parseFloat(mxUtils.getValue(this.style, 'size', this.size))) * w);

      c.moveTo(0, h / 2);
      c.lineTo(s, 0);
      c.lineTo(w - dx, 0);
      c.quadTo(w, 0, w, h / 2);
      c.quadTo(w, h, w - dx, h);
      c.lineTo(s, h);
      c.close();
      c.end();
    };

    mxCellRenderer.registerShape('display', DisplayShape);

    // FilledEdge shape
    function FilledEdge() {
      mxConnector.call(this);
    }
    mxUtils.extend(FilledEdge, mxConnector);

    FilledEdge.prototype.origPaintEdgeShape = FilledEdge.prototype.paintEdgeShape;
    FilledEdge.prototype.paintEdgeShape = function (c, pts, rounded) {
      // Markers modify incoming points array
      const temp = [];

      for (let i = 0; i < pts.length; i++) {
        temp.push(mxUtils.clone(pts[i]));
      }

      // paintEdgeShape resets dashed to false
      const dashed = c.state.dashed;
      const fixDash = c.state.fixDash;
      FilledEdge.prototype.origPaintEdgeShape.apply(this, [c, temp, rounded]);

      if (c.state.strokeWidth >= 3) {
        const fillClr = mxUtils.getValue(this.style, 'fillColor', null);

        if (fillClr != null) {
          c.setStrokeColor(fillClr);
          c.setStrokeWidth(c.state.strokeWidth - 2);
          c.setDashed(dashed, fixDash);

          FilledEdge.prototype.origPaintEdgeShape.apply(this, [c, pts, rounded]);
        }
      }
    };

    // Registers the link shape
    mxCellRenderer.registerShape('filledEdge', FilledEdge);

    // Implements custom colors for shapes
    if (typeof StyleFormatPanel !== 'undefined') {
      (function () {
        const styleFormatPanelGetCustomColors = StyleFormatPanel.prototype.getCustomColors;

        StyleFormatPanel.prototype.getCustomColors = function () {
          const ss = this.format.getSelectionState();
          const result = styleFormatPanelGetCustomColors.apply(this, arguments);

          if (ss.style.shape == 'umlFrame') {
            result.push({
              title: mxResources.get('laneColor'),
              key: 'swimlaneFillColor',
              defaultValue: '#ffffff'
            });
          }

          return result;
        };
      }());
    }

    // Registers and defines the custom marker
    mxMarker.addMarker('dash', (c, shape, type, pe, unitX, unitY, size, source, sw, filled) => {
      let nx = unitX * (size + sw + 1);
      let ny = unitY * (size + sw + 1);

      return function () {
        c.begin();
        c.moveTo(pe.x - nx / 2 - ny / 2, pe.y - ny / 2 + nx / 2);
        c.lineTo(pe.x + ny / 2 - 3 * nx / 2, pe.y - 3 * ny / 2 - nx / 2);
        c.stroke();
      };
    });

    // Registers and defines the custom marker
    mxMarker.addMarker('cross', (c, shape, type, pe, unitX, unitY, size, source, sw, filled) => {
      let nx = unitX * (size + sw + 1);
      let ny = unitY * (size + sw + 1);

      return function () {
        c.begin();
        c.moveTo(pe.x - nx / 2 - ny / 2, pe.y - ny / 2 + nx / 2);
        c.lineTo(pe.x + ny / 2 - 3 * nx / 2, pe.y - 3 * ny / 2 - nx / 2);
        c.moveTo(pe.x - nx / 2 + ny / 2, pe.y - ny / 2 - nx / 2);
        c.lineTo(pe.x - ny / 2 - 3 * nx / 2, pe.y - 3 * ny / 2 + nx / 2);
        c.stroke();
      };
    });

    function circleMarker(c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
      const a = size / 2;
      var size = size + sw;

      const pt = pe.clone();

      pe.x -= unitX * (2 * size + sw);
      pe.y -= unitY * (2 * size + sw);

      unitX *= (size + sw);
      unitY *= (size + sw);

      return function () {
        c.ellipse(pt.x - unitX - size, pt.y - unitY - size, 2 * size, 2 * size);

        if (filled) {
          c.fillAndStroke();
        } else {
          c.stroke();
        }
      };
    }

    mxMarker.addMarker('circle', circleMarker);
    mxMarker.addMarker('circlePlus', function (c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
      const pt = pe.clone();
      const fn = circleMarker.apply(this, arguments);
      const nx = unitX * (size + 2 * sw); // (size + sw + 1);
      const ny = unitY * (size + 2 * sw); // (size + sw + 1);

      return function () {
        fn.apply(this, arguments);

        c.begin();
        c.moveTo(pt.x - unitX * (sw), pt.y - unitY * (sw));
        c.lineTo(pt.x - 2 * nx + unitX * (sw), pt.y - 2 * ny + unitY * (sw));
        c.moveTo(pt.x - nx - ny + unitY * sw, pt.y - ny + nx - unitX * sw);
        c.lineTo(pt.x + ny - nx - unitY * sw, pt.y - ny - nx + unitX * sw);
        c.stroke();
      };
    });

    mxMarker.addMarker('async', (c, shape, type, pe, unitX, unitY, size, source, sw, filled) => {
      // The angle of the forward facing arrow sides against the x axis is
      // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
      // only half the strokewidth is processed ).
      let endOffsetX = unitX * sw * 1.118;
      let endOffsetY = unitY * sw * 1.118;

      unitX *= (size + sw);
      unitY *= (size + sw);

      let pt = pe.clone();
      pt.x -= endOffsetX;
      pt.y -= endOffsetY;

      let f = 1;
      pe.x += -unitX * f - endOffsetX;
      pe.y += -unitY * f - endOffsetY;

      return function () {
        c.begin();
        c.moveTo(pt.x, pt.y);

        if (source) {
          c.lineTo(pt.x - unitX - unitY / 2, pt.y - unitY + unitX / 2);
        } else {
          c.lineTo(pt.x + unitY / 2 - unitX, pt.y - unitY - unitX / 2);
        }

        c.lineTo(pt.x - unitX, pt.y - unitY);
        c.close();

        if (filled) {
          c.fillAndStroke();
        } else {
          c.stroke();
        }
      };
    });

    function createOpenAsyncArrow(widthFactor) {
      widthFactor = (widthFactor != null) ? widthFactor : 2;

      return function (c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
        unitX *= (size + sw);
        unitY *= (size + sw);

        const pt = pe.clone();

        return function () {
          c.begin();
          c.moveTo(pt.x, pt.y);

          if (source) {
            c.lineTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);
          } else {
            c.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
          }

          c.stroke();
        };
      };
    }

    mxMarker.addMarker('openAsync', createOpenAsyncArrow(2));

    function arrow(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) {
      // The angle of the forward facing arrow sides against the x axis is
      // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
      // only half the strokewidth is processed ).
      const endOffsetX = unitX * sw * 1.118;
      const endOffsetY = unitY * sw * 1.118;

      unitX *= (size + sw);
      unitY *= (size + sw);

      const pt = pe.clone();
      pt.x -= endOffsetX;
      pt.y -= endOffsetY;

      const f = (type != mxConstants.ARROW_CLASSIC && type != mxConstants.ARROW_CLASSIC_THIN) ? 1 : 3 / 4;
      pe.x += -unitX * f - endOffsetX;
      pe.y += -unitY * f - endOffsetY;

      return function () {
        canvas.begin();
        canvas.moveTo(pt.x, pt.y);
        canvas.lineTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);

        if (type == mxConstants.ARROW_CLASSIC || type == mxConstants.ARROW_CLASSIC_THIN) {
          canvas.lineTo(pt.x - unitX * 3 / 4, pt.y - unitY * 3 / 4);
        }

        canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
        canvas.close();

        if (filled) {
          canvas.fillAndStroke();
        } else {
          canvas.stroke();
        }
      };
    }

    // Handlers are only added if mxVertexHandler is defined (ie. not in embedded graph)
    if (typeof mxVertexHandler !== 'undefined') {
      function createHandle(state, keys, getPositionFn, setPositionFn, ignoreGrid, redrawEdges) {
        const handle = new mxHandle(state, null, mxVertexHandler.prototype.secondaryHandleImage);

        handle.execute = function () {
          for (let i = 0; i < keys.length; i++) {
            this.copyStyle(keys[i]);
          }
        };

        handle.getPosition = getPositionFn;
        handle.setPosition = setPositionFn;
        handle.ignoreGrid = (ignoreGrid != null) ? ignoreGrid : true;

        // Overridden to update connected edges
        if (redrawEdges) {
          const positionChanged = handle.positionChanged;

          handle.positionChanged = function () {
            positionChanged.apply(this, arguments);

            // Redraws connected edges TODO: Include child edges
            state.view.invalidate(this.state.cell);
            state.view.validate();
          };
        }

        return handle;
      }

      function createArcHandle(state, yOffset) {
        return createHandle(state, [mxConstants.STYLE_ARCSIZE], (bounds) => {
          var tmp = (yOffset != null) ? yOffset : bounds.height / 8;

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
            var arcSize = mxUtils.getValue(state.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;

            return new mxPoint(bounds.x + bounds.width - Math.min(bounds.width / 2, arcSize), bounds.y + tmp);
          }

          var arcSize = Math.max(0, parseFloat(mxUtils.getValue(state.style,
            mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100))) / 100;

          return new mxPoint(bounds.x + bounds.width - Math.min(Math.max(bounds.width / 2, bounds.height / 2),
            Math.min(bounds.width, bounds.height) * arcSize), bounds.y + tmp);

        }, function (bounds, pt, me) {
          if (mxUtils.getValue(state.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
            this.state.style[mxConstants.STYLE_ARCSIZE] = Math.round(Math.max(0, Math.min(bounds.width,
              (bounds.x + bounds.width - pt.x) * 2)));
          } else {
            const f = Math.min(50, Math.max(0, (bounds.width - pt.x + bounds.x) * 100 /
              Math.min(bounds.width, bounds.height)));
            this.state.style[mxConstants.STYLE_ARCSIZE] = Math.round(f);
          }
        });
      }

      function createArcHandleFunction() {
        return function (state) {
          const handles = [];

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
            handles.push(createArcHandle(state));
          }

          return handles;
        };
      }

      function createTrapezoidHandleFunction(max) {
        return function (state) {
          const handles = [createHandle(state, ['size'], function (bounds) {
            const size = Math.max(0, Math.min(max, parseFloat(mxUtils.getValue(this.state.style, 'size', TrapezoidShape.prototype.size))));

            return new mxPoint(bounds.x + size * bounds.width * 0.75, bounds.y + bounds.height / 4);
          }, function (bounds, pt) {
            this.state.style.size = Math.max(0, Math.min(max, (pt.x - bounds.x) / (bounds.width * 0.75)));
          }, null, true)];

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
            handles.push(createArcHandle(state));
          }

          return handles;
        };
      }

      function createDisplayHandleFunction(defaultValue, allowArcHandle, max, redrawEdges, fixedDefaultValue) {
        max = (max != null) ? max : 1;

        return function (state) {
          const handles = [createHandle(state, ['size'], function (bounds) {
            const fixed = (fixedDefaultValue != null) ? mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0' : null;
            const size = parseFloat(mxUtils.getValue(this.state.style, 'size', (fixed) ? fixedDefaultValue : defaultValue));

            return new mxPoint(bounds.x + Math.max(0, Math.min(bounds.width, size * ((fixed) ? 1 : bounds.width))), bounds.getCenterY());
          }, function (bounds, pt, me) {
            const fixed = (fixedDefaultValue != null) ? mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0' : null;
            let size = (fixed) ? (pt.x - bounds.x) : Math.max(0, Math.min(max, (pt.x - bounds.x) / bounds.width));

            if (fixed && !mxEvent.isAltDown(me.getEvent())) {
              size = state.view.graph.snap(size);
            }

            this.state.style.size = size;
          }, null, redrawEdges)];

          if (allowArcHandle && mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
            handles.push(createArcHandle(state));
          }

          return handles;
        };
      }

      function createCubeHandleFunction(factor, defaultValue, allowArcHandle) {
        return function (state) {
          const handles = [createHandle(state, ['size'], function (bounds) {
            const size = Math.max(0, Math.min(bounds.width, Math.min(bounds.height, parseFloat(
              mxUtils.getValue(this.state.style, 'size', defaultValue),
            )))) * factor;

            return new mxPoint(bounds.x + size, bounds.y + size);
          }, function (bounds, pt) {
            this.state.style.size = Math.round(Math.max(0, Math.min(Math.min(bounds.width, pt.x - bounds.x),
              Math.min(bounds.height, pt.y - bounds.y))) / factor);
          })];

          if (allowArcHandle && mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
            handles.push(createArcHandle(state));
          }

          return handles;
        };
      }

      function createArrowHandleFunction(maxSize) {
        return function (state) {
          return [createHandle(state, ['arrowWidth', 'arrowSize'], function (bounds) {
            const aw = Math.max(0, Math.min(1, mxUtils.getValue(this.state.style, 'arrowWidth', SingleArrowShape.prototype.arrowWidth)));
            const as = Math.max(0, Math.min(maxSize, mxUtils.getValue(this.state.style, 'arrowSize', SingleArrowShape.prototype.arrowSize)));

            return new mxPoint(bounds.x + (1 - as) * bounds.width, bounds.y + (1 - aw) * bounds.height / 2);
          }, function (bounds, pt) {
            this.state.style.arrowWidth = Math.max(0, Math.min(1, Math.abs(bounds.y + bounds.height / 2 - pt.y) / bounds.height * 2));
            this.state.style.arrowSize = Math.max(0, Math.min(maxSize, (bounds.x + bounds.width - pt.x) / (bounds.width)));
          })];
        };
      }

      function createEdgeHandle(state, keys, start, getPosition, setPosition) {
        return createHandle(state, keys, function (bounds) {
          const pts = state.absolutePoints;
          const n = pts.length - 1;

          const tr = state.view.translate;
          const s = state.view.scale;

          const p0 = (start) ? pts[0] : pts[n];
          const p1 = (start) ? pts[1] : pts[n - 1];
          const dx = (start) ? p1.x - p0.x : p1.x - p0.x;
          const dy = (start) ? p1.y - p0.y : p1.y - p0.y;

          const dist = Math.sqrt(dx * dx + dy * dy);

          const pt = getPosition.call(this, dist, dx / dist, dy / dist, p0, p1);

          return new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y);
        }, function (bounds, pt, me) {
          const pts = state.absolutePoints;
          const n = pts.length - 1;

          const tr = state.view.translate;
          const s = state.view.scale;

          const p0 = (start) ? pts[0] : pts[n];
          const p1 = (start) ? pts[1] : pts[n - 1];
          const dx = (start) ? p1.x - p0.x : p1.x - p0.x;
          const dy = (start) ? p1.y - p0.y : p1.y - p0.y;

          const dist = Math.sqrt(dx * dx + dy * dy);
          pt.x = (pt.x + tr.x) * s;
          pt.y = (pt.y + tr.y) * s;

          setPosition.call(this, dist, dx / dist, dy / dist, p0, p1, pt, me);
        });
      }

      function createEdgeWidthHandle(state, start, spacing) {
        return createEdgeHandle(state, ['width'], start, (dist, nx, ny, p0, p1) => {
          let w = state.shape.getEdgeWidth() * state.view.scale + spacing;

          return new mxPoint(p0.x + nx * dist / 4 + ny * w / 2, p0.y + ny * dist / 4 - nx * w / 2);
        }, (dist, nx, ny, p0, p1, pt) => {
          let w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
          state.style.width = Math.round(w * 2) / state.view.scale - spacing;
        });
      }

      function ptLineDistance(x1, y1, x2, y2, x0, y0) {
        return Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) / Math.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1));
      }

      const handleFactory = {
        link(state) {
          let spacing = 10;

          return [createEdgeWidthHandle(state, true, spacing), createEdgeWidthHandle(state, false, spacing)];
        },
        flexArrow(state) {
          // Do not use state.shape.startSize/endSize since it is cached
          let tol = state.view.graph.gridSize / state.view.scale;
          let handles = [];

          if (mxUtils.getValue(state.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE) {
            handles.push(createEdgeHandle(state, ['width', mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE], true, (dist, nx, ny, p0, p1) => {
              var w = (state.shape.getEdgeWidth() - state.shape.strokewidth) * state.view.scale;
              var l = mxUtils.getNumber(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) * 3 * state.view.scale;

              return new mxPoint(p0.x + nx * (l + state.shape.strokewidth * state.view.scale) + ny * w / 2,
                p0.y + ny * (l + state.shape.strokewidth * state.view.scale) - nx * w / 2);
            }, (dist, nx, ny, p0, p1, pt, me) => {
              var w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
              var l = mxUtils.ptLineDist(p0.x, p0.y, p0.x + ny, p0.y - nx, pt.x, pt.y);

              state.style[mxConstants.STYLE_STARTSIZE] = Math.round((l - state.shape.strokewidth) * 100 / 3) / 100 / state.view.scale;
              state.style['width'] = Math.round(w * 2) / state.view.scale;

              // Applies to opposite side
              if (mxEvent.isControlDown(me.getEvent())) {
                state.style[mxConstants.STYLE_ENDSIZE] = state.style[mxConstants.STYLE_STARTSIZE];
              }

              // Snaps to end geometry
              if (!mxEvent.isAltDown(me.getEvent())) {
                if (Math.abs(parseFloat(state.style[mxConstants.STYLE_STARTSIZE]) - parseFloat(state.style[mxConstants.STYLE_ENDSIZE])) < tol / 6) {
                  state.style[mxConstants.STYLE_STARTSIZE] = state.style[mxConstants.STYLE_ENDSIZE];
                }
              }
            }));

            handles.push(createEdgeHandle(state, ['startWidth', 'endWidth', mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE], true, (dist, nx, ny, p0, p1) => {
              var w = (state.shape.getStartArrowWidth() - state.shape.strokewidth) * state.view.scale;
              var l = mxUtils.getNumber(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) * 3 * state.view.scale;

              return new mxPoint(p0.x + nx * (l + state.shape.strokewidth * state.view.scale) + ny * w / 2,
                p0.y + ny * (l + state.shape.strokewidth * state.view.scale) - nx * w / 2);
            }, (dist, nx, ny, p0, p1, pt, me) => {
              var w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
              var l = mxUtils.ptLineDist(p0.x, p0.y, p0.x + ny, p0.y - nx, pt.x, pt.y);

              state.style[mxConstants.STYLE_STARTSIZE] = Math.round((l - state.shape.strokewidth) * 100 / 3) / 100 / state.view.scale;
              state.style['startWidth'] = Math.max(0, Math.round(w * 2) - state.shape.getEdgeWidth()) / state.view.scale;

              // Applies to opposite side
              if (mxEvent.isControlDown(me.getEvent())) {
                state.style[mxConstants.STYLE_ENDSIZE] = state.style[mxConstants.STYLE_STARTSIZE];
                state.style['endWidth'] = state.style['startWidth'];
              }

              // Snaps to endWidth
              if (!mxEvent.isAltDown(me.getEvent())) {
                if (Math.abs(parseFloat(state.style[mxConstants.STYLE_STARTSIZE]) - parseFloat(state.style[mxConstants.STYLE_ENDSIZE])) < tol / 6) {
                  state.style[mxConstants.STYLE_STARTSIZE] = state.style[mxConstants.STYLE_ENDSIZE];
                }

                if (Math.abs(parseFloat(state.style['startWidth']) - parseFloat(state.style['endWidth'])) < tol) {
                  state.style['startWidth'] = state.style['endWidth'];
                }
              }
            }));
          }

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE) {
            handles.push(createEdgeHandle(state, ['width', mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE], false, (dist, nx, ny, p0, p1) => {
              var w = (state.shape.getEdgeWidth() - state.shape.strokewidth) * state.view.scale;
              var l = mxUtils.getNumber(state.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) * 3 * state.view.scale;

              return new mxPoint(p0.x + nx * (l + state.shape.strokewidth * state.view.scale) - ny * w / 2,
                p0.y + ny * (l + state.shape.strokewidth * state.view.scale) + nx * w / 2);
            }, (dist, nx, ny, p0, p1, pt, me) => {
              var w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
              var l = mxUtils.ptLineDist(p0.x, p0.y, p0.x + ny, p0.y - nx, pt.x, pt.y);

              state.style[mxConstants.STYLE_ENDSIZE] = Math.round((l - state.shape.strokewidth) * 100 / 3) / 100 / state.view.scale;
              state.style['width'] = Math.round(w * 2) / state.view.scale;

              // Applies to opposite side
              if (mxEvent.isControlDown(me.getEvent())) {
                state.style[mxConstants.STYLE_STARTSIZE] = state.style[mxConstants.STYLE_ENDSIZE];
              }

              // Snaps to start geometry
              if (!mxEvent.isAltDown(me.getEvent())) {
                if (Math.abs(parseFloat(state.style[mxConstants.STYLE_ENDSIZE]) - parseFloat(state.style[mxConstants.STYLE_STARTSIZE])) < tol / 6) {
                  state.style[mxConstants.STYLE_ENDSIZE] = state.style[mxConstants.STYLE_STARTSIZE];
                }
              }
            }));

            handles.push(createEdgeHandle(state, ['startWidth', 'endWidth', mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE], false, (dist, nx, ny, p0, p1) => {
              var w = (state.shape.getEndArrowWidth() - state.shape.strokewidth) * state.view.scale;
              var l = mxUtils.getNumber(state.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) * 3 * state.view.scale;

              return new mxPoint(p0.x + nx * (l + state.shape.strokewidth * state.view.scale) - ny * w / 2,
                p0.y + ny * (l + state.shape.strokewidth * state.view.scale) + nx * w / 2);
            }, (dist, nx, ny, p0, p1, pt, me) => {
              var w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
              var l = mxUtils.ptLineDist(p0.x, p0.y, p0.x + ny, p0.y - nx, pt.x, pt.y);

              state.style[mxConstants.STYLE_ENDSIZE] = Math.round((l - state.shape.strokewidth) * 100 / 3) / 100 / state.view.scale;
              state.style['endWidth'] = Math.max(0, Math.round(w * 2) - state.shape.getEdgeWidth()) / state.view.scale;

              // Applies to opposite side
              if (mxEvent.isControlDown(me.getEvent())) {
                state.style[mxConstants.STYLE_STARTSIZE] = state.style[mxConstants.STYLE_ENDSIZE];
                state.style['startWidth'] = state.style['endWidth'];
              }

              // Snaps to start geometry
              if (!mxEvent.isAltDown(me.getEvent())) {
                if (Math.abs(parseFloat(state.style[mxConstants.STYLE_ENDSIZE]) - parseFloat(state.style[mxConstants.STYLE_STARTSIZE])) < tol / 6) {
                  state.style[mxConstants.STYLE_ENDSIZE] = state.style[mxConstants.STYLE_STARTSIZE];
                }

                if (Math.abs(parseFloat(state.style['endWidth']) - parseFloat(state.style['startWidth'])) < tol) {
                  state.style['endWidth'] = state.style['startWidth'];
                }
              }
            }));
          }

          return handles;
        },
        swimlane(state) {
          let handles = [createHandle(state, [mxConstants.STYLE_STARTSIZE], function (bounds) {
            var size = parseFloat(mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));

            if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 1) {
              return new mxPoint(bounds.getCenterX(), bounds.y + Math.max(0, Math.min(bounds.height, size)));
            }

            return new mxPoint(bounds.x + Math.max(0, Math.min(bounds.width, size)), bounds.getCenterY());

          }, function (bounds, pt) {
            state.style[mxConstants.STYLE_STARTSIZE] = (mxUtils.getValue(this.state.style, mxConstants.STYLE_HORIZONTAL, 1) == 1) ?
              Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y))) :
              Math.round(Math.max(0, Math.min(bounds.width, pt.x - bounds.x)));
          })];

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED)) {
            let size = parseFloat(mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
            handles.push(createArcHandle(state, size / 2));
          }

          return handles;
        },
        label: createArcHandleFunction(),
        ext: createArcHandleFunction(),
        rectangle: createArcHandleFunction(),
        triangle: createArcHandleFunction(),
        rhombus: createArcHandleFunction(),
        umlLifeline(state) {
          return [createHandle(state, ['size'], function (bounds) {
            let size = Math.max(0, Math.min(bounds.height, parseFloat(mxUtils.getValue(this.state.style, 'size', UmlLifeline.prototype.size))));

            return new mxPoint(bounds.getCenterX(), bounds.y + size);
          }, function (bounds, pt) {
            this.state.style.size = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
          }, false)];
        },
        umlFrame(state) {
          let handles = [createHandle(state, ['width', 'height'], function (bounds) {
            let w0 = Math.max(UmlFrame.prototype.corner, Math.min(bounds.width, mxUtils.getValue(this.state.style, 'width', UmlFrame.prototype.width)));
            let h0 = Math.max(UmlFrame.prototype.corner * 1.5, Math.min(bounds.height, mxUtils.getValue(this.state.style, 'height', UmlFrame.prototype.height)));

            return new mxPoint(bounds.x + w0, bounds.y + h0);
          }, function (bounds, pt) {
            this.state.style.width = Math.round(Math.max(UmlFrame.prototype.corner, Math.min(bounds.width, pt.x - bounds.x)));
            this.state.style.height = Math.round(Math.max(UmlFrame.prototype.corner * 1.5, Math.min(bounds.height, pt.y - bounds.y)));
          }, false)];

          return handles;
        },
        process(state) {
          let handles = [createHandle(state, ['size'], function (bounds) {
            let size = Math.max(0, Math.min(0.5, parseFloat(mxUtils.getValue(this.state.style, 'size', ProcessShape.prototype.size))));

            return new mxPoint(bounds.x + bounds.width * size, bounds.y + bounds.height / 4);
          }, function (bounds, pt) {
            this.state.style.size = Math.max(0, Math.min(0.5, (pt.x - bounds.x) / bounds.width));
          })];

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
            handles.push(createArcHandle(state));
          }

          return handles;
        },
        cross(state) {
          return [createHandle(state, ['size'], function (bounds) {
            let m = Math.min(bounds.width, bounds.height);
            let size = Math.max(0, Math.min(1, mxUtils.getValue(this.state.style, 'size', CrossShape.prototype.size))) * m / 2;

            return new mxPoint(bounds.getCenterX() - size, bounds.getCenterY() - size);
          }, function (bounds, pt) {
            let m = Math.min(bounds.width, bounds.height);
            this.state.style.size = Math.max(0, Math.min(1, Math.min((Math.max(0, bounds.getCenterY() - pt.y) / m) * 2,
              (Math.max(0, bounds.getCenterX() - pt.x) / m) * 2)));
          })];
        },
        note(state) {
          return [createHandle(state, ['size'], function (bounds) {
            let size = Math.max(0, Math.min(bounds.width, Math.min(bounds.height, parseFloat(
              mxUtils.getValue(this.state.style, 'size', NoteShape.prototype.size)
            ))));

            return new mxPoint(bounds.x + bounds.width - size, bounds.y + size);
          }, function (bounds, pt) {
            this.state.style.size = Math.round(Math.max(0, Math.min(Math.min(bounds.width, bounds.x + bounds.width - pt.x),
              Math.min(bounds.height, pt.y - bounds.y))));
          })];
        },
        manualInput(state) {
          let handles = [createHandle(state, ['size'], function (bounds) {
            let size = Math.max(0, Math.min(bounds.height, mxUtils.getValue(this.state.style, 'size', ManualInputShape.prototype.size)));

            return new mxPoint(bounds.x + bounds.width / 4, bounds.y + size * 3 / 4);
          }, function (bounds, pt) {
            this.state.style.size = Math.round(Math.max(0, Math.min(bounds.height, (pt.y - bounds.y) * 4 / 3)));
          })];

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
            handles.push(createArcHandle(state));
          }

          return handles;
        },
        dataStorage(state) {
          return [createHandle(state, ['size'], function (bounds) {
            let size = Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.state.style, 'size', DataStorageShape.prototype.size))));

            return new mxPoint(bounds.x + (1 - size) * bounds.width, bounds.getCenterY());
          }, function (bounds, pt) {
            this.state.style.size = Math.max(0, Math.min(1, (bounds.x + bounds.width - pt.x) / bounds.width));
          })];
        },
        callout(state) {
          let handles = [createHandle(state, ['size', 'position'], function (bounds) {
            let size = Math.max(0, Math.min(bounds.height, mxUtils.getValue(this.state.style, 'size', CalloutShape.prototype.size)));
            let position = Math.max(0, Math.min(1, mxUtils.getValue(this.state.style, 'position', CalloutShape.prototype.position)));
            let base = Math.max(0, Math.min(bounds.width, mxUtils.getValue(this.state.style, 'base', CalloutShape.prototype.base)));

            return new mxPoint(bounds.x + position * bounds.width, bounds.y + bounds.height - size);
          }, function (bounds, pt) {
            let base = Math.max(0, Math.min(bounds.width, mxUtils.getValue(this.state.style, 'base', CalloutShape.prototype.base)));
            this.state.style.size = Math.round(Math.max(0, Math.min(bounds.height, bounds.y + bounds.height - pt.y)));
            this.state.style.position = Math.round(Math.max(0, Math.min(1, (pt.x - bounds.x) / bounds.width)) * 100) / 100;
          }), createHandle(state, ['position2'], function (bounds) {
            let position2 = Math.max(0, Math.min(1, mxUtils.getValue(this.state.style, 'position2', CalloutShape.prototype.position2)));

            return new mxPoint(bounds.x + position2 * bounds.width, bounds.y + bounds.height);
          }, function (bounds, pt) {
            this.state.style.position2 = Math.round(Math.max(0, Math.min(1, (pt.x - bounds.x) / bounds.width)) * 100) / 100;
          }), createHandle(state, ['base'], function (bounds) {
            let size = Math.max(0, Math.min(bounds.height, mxUtils.getValue(this.state.style, 'size', CalloutShape.prototype.size)));
            let position = Math.max(0, Math.min(1, mxUtils.getValue(this.state.style, 'position', CalloutShape.prototype.position)));
            let base = Math.max(0, Math.min(bounds.width, mxUtils.getValue(this.state.style, 'base', CalloutShape.prototype.base)));

            return new mxPoint(bounds.x + Math.min(bounds.width, position * bounds.width + base), bounds.y + bounds.height - size);
          }, function (bounds, pt) {
            let position = Math.max(0, Math.min(1, mxUtils.getValue(this.state.style, 'position', CalloutShape.prototype.position)));

            this.state.style.base = Math.round(Math.max(0, Math.min(bounds.width, pt.x - bounds.x - position * bounds.width)));
          })];

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
            handles.push(createArcHandle(state));
          }

          return handles;
        },
        internalStorage(state) {
          let handles = [createHandle(state, ['dx', 'dy'], function (bounds) {
            let dx = Math.max(0, Math.min(bounds.width, mxUtils.getValue(this.state.style, 'dx', InternalStorageShape.prototype.dx)));
            let dy = Math.max(0, Math.min(bounds.height, mxUtils.getValue(this.state.style, 'dy', InternalStorageShape.prototype.dy)));

            return new mxPoint(bounds.x + dx, bounds.y + dy);
          }, function (bounds, pt) {
            this.state.style.dx = Math.round(Math.max(0, Math.min(bounds.width, pt.x - bounds.x)));
            this.state.style.dy = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
          })];

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
            handles.push(createArcHandle(state));
          }

          return handles;
        },
        corner(state) {
          return [createHandle(state, ['dx', 'dy'], function (bounds) {
            let dx = Math.max(0, Math.min(bounds.width, mxUtils.getValue(this.state.style, 'dx', CornerShape.prototype.dx)));
            let dy = Math.max(0, Math.min(bounds.height, mxUtils.getValue(this.state.style, 'dy', CornerShape.prototype.dy)));

            return new mxPoint(bounds.x + dx, bounds.y + dy);
          }, function (bounds, pt) {
            this.state.style.dx = Math.round(Math.max(0, Math.min(bounds.width, pt.x - bounds.x)));
            this.state.style.dy = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
          })];
        },
        tee(state) {
          return [createHandle(state, ['dx', 'dy'], function (bounds) {
            let dx = Math.max(0, Math.min(bounds.width, mxUtils.getValue(this.state.style, 'dx', TeeShape.prototype.dx)));
            let dy = Math.max(0, Math.min(bounds.height, mxUtils.getValue(this.state.style, 'dy', TeeShape.prototype.dy)));

            return new mxPoint(bounds.x + (bounds.width + dx) / 2, bounds.y + dy);
          }, function (bounds, pt) {
            this.state.style.dx = Math.round(Math.max(0, Math.min(bounds.width / 2, (pt.x - bounds.x - bounds.width / 2)) * 2));
            this.state.style.dy = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
          })];
        },
        singleArrow: createArrowHandleFunction(1),
        doubleArrow: createArrowHandleFunction(0.5),
        folder(state) {
          return [createHandle(state, ['tabWidth', 'tabHeight'], function (bounds) {
            let tw = Math.max(0, Math.min(bounds.width, mxUtils.getValue(this.state.style, 'tabWidth', FolderShape.prototype.tabWidth)));
            let th = Math.max(0, Math.min(bounds.height, mxUtils.getValue(this.state.style, 'tabHeight', FolderShape.prototype.tabHeight)));

            if (mxUtils.getValue(this.state.style, 'tabPosition', FolderShape.prototype.tabPosition) == mxConstants.ALIGN_RIGHT) {
              tw = bounds.width - tw;
            }

            return new mxPoint(bounds.x + tw, bounds.y + th);
          }, function (bounds, pt) {
            let tw = Math.max(0, Math.min(bounds.width, pt.x - bounds.x));

            if (mxUtils.getValue(this.state.style, 'tabPosition', FolderShape.prototype.tabPosition) == mxConstants.ALIGN_RIGHT) {
              tw = bounds.width - tw;
            }

            this.state.style.tabWidth = Math.round(tw);
            this.state.style.tabHeight = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
          })];
        },
        document(state) {
          return [createHandle(state, ['size'], function (bounds) {
            let size = Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.state.style, 'size', DocumentShape.prototype.size))));

            return new mxPoint(bounds.x + 3 * bounds.width / 4, bounds.y + (1 - size) * bounds.height);
          }, function (bounds, pt) {
            this.state.style.size = Math.max(0, Math.min(1, (bounds.y + bounds.height - pt.y) / bounds.height));
          })];
        },
        tape(state) {
          return [createHandle(state, ['size'], function (bounds) {
            let size = Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.state.style, 'size', TapeShape.prototype.size))));

            return new mxPoint(bounds.getCenterX(), bounds.y + size * bounds.height / 2);
          }, function (bounds, pt) {
            this.state.style.size = Math.max(0, Math.min(1, ((pt.y - bounds.y) / bounds.height) * 2));
          })];
        },
        offPageConnector(state) {
          return [createHandle(state, ['size'], function (bounds) {
            let size = Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.state.style, 'size', OffPageConnectorShape.prototype.size))));

            return new mxPoint(bounds.getCenterX(), bounds.y + (1 - size) * bounds.height);
          }, function (bounds, pt) {
            this.state.style.size = Math.max(0, Math.min(1, (bounds.y + bounds.height - pt.y) / bounds.height));
          })];
        },
        step: createDisplayHandleFunction(StepShape.prototype.size, true, null, true, StepShape.prototype.fixedSize),
        hexagon: createDisplayHandleFunction(HexagonShape.prototype.size, true, 0.5, true),
        curlyBracket: createDisplayHandleFunction(CurlyBracketShape.prototype.size, false),
        display: createDisplayHandleFunction(DisplayShape.prototype.size, false),
        cube: createCubeHandleFunction(1, CubeShape.prototype.size, false),
        card: createCubeHandleFunction(0.5, CardShape.prototype.size, true),
        loopLimit: createCubeHandleFunction(0.5, LoopLimitShape.prototype.size, true),
        trapezoid: createTrapezoidHandleFunction(0.5),
        parallelogram: createTrapezoidHandleFunction(1),
      };

      // // Exposes custom handles
      // Graph.createHandle = createHandle;
      // Graph.handleFactory = handleFactory;

      mxVertexHandler.prototype.createCustomHandles = function () {
        // Not rotatable means locked
        if (this.state.view.graph.getSelectionCount() == 1) {
          if (this.graph.isCellRotatable(this.state.cell))
          // LATER: Make locked state independent of rotatable flag, fix toggle if default is false
          // if (this.graph.isCellResizable(this.state.cell) || this.graph.isCellMovable(this.state.cell))
          {
            let name = this.state.style.shape;

            if (mxCellRenderer.defaultShapes[name] == null &&
              mxStencilRegistry.getStencil(name) == null) {
              name = mxConstants.SHAPE_RECTANGLE;
            }

            let fn = handleFactory[name];

            if (fn == null && this.state.shape != null && this.state.shape.isRoundable()) {
              fn = handleFactory[mxConstants.SHAPE_RECTANGLE];
            }

            if (fn != null) {
              return fn(this.state);
            }
          }
        }

        return null;
      };

      mxEdgeHandler.prototype.createCustomHandles = function () {
        if (this.state.view.graph.getSelectionCount() == 1) {
          let name = this.state.style.shape;

          if (mxCellRenderer.defaultShapes[name] == null &&
            mxStencilRegistry.getStencil(name) == null) {
            name = mxConstants.SHAPE_CONNECTOR;
          }

          const fn = handleFactory[name];

          if (fn != null) {
            return fn(this.state);
          }
        }

        return null;
      };
    } else {
      // // Dummy entries to avoid NPE in embed mode
      // Graph.createHandle = function () {};
      // Graph.handleFactory = {};
    }

    let isoHVector = new mxPoint(1, 0);
    let isoVVector = new mxPoint(1, 0);

    const alpha1 = mxUtils.toRadians(-30);

    const cos1 = Math.cos(alpha1);
    const sin1 = Math.sin(alpha1);

    isoHVector = mxUtils.getRotatedPoint(isoHVector, cos1, sin1);

    const alpha2 = mxUtils.toRadians(-150);

    const cos2 = Math.cos(alpha2);
    const sin2 = Math.sin(alpha2);

    isoVVector = mxUtils.getRotatedPoint(isoVVector, cos2, sin2);

    mxEdgeStyle.IsometricConnector = function (state, source, target, points, result) {
      const view = state.view;
      let pt = (points != null && points.length > 0) ? points[0] : null;
      const pts = state.absolutePoints;
      let p0 = pts[0];
      let pe = pts[pts.length - 1];

      if (pt != null) {
        pt = view.transformControlPoint(state, pt);
      }

      if (p0 == null) {
        if (source != null) {
          p0 = new mxPoint(source.getCenterX(), source.getCenterY());
        }
      }

      if (pe == null) {
        if (target != null) {
          pe = new mxPoint(target.getCenterX(), target.getCenterY());
        }
      }

      const a1 = isoHVector.x;
      const a2 = isoHVector.y;

      const b1 = isoVVector.x;
      const b2 = isoVVector.y;

      const elbow = mxUtils.getValue(state.style, 'elbow', 'horizontal') == 'horizontal';

      if (pe != null && p0 != null) {
        let last = p0;

        function isoLineTo(x, y, ignoreFirst) {
          const c1 = x - last.x;
          const c2 = y - last.y;

          // Solves for isometric base vectors
          const h = (b2 * c1 - b1 * c2) / (a1 * b2 - a2 * b1);
          const v = (a2 * c1 - a1 * c2) / (a2 * b1 - a1 * b2);

          if (elbow) {
            if (ignoreFirst) {
              last = new mxPoint(last.x + a1 * h, last.y + a2 * h);
              result.push(last);
            }

            last = new mxPoint(last.x + b1 * v, last.y + b2 * v);
            result.push(last);
          } else {
            if (ignoreFirst) {
              last = new mxPoint(last.x + b1 * v, last.y + b2 * v);
              result.push(last);
            }

            last = new mxPoint(last.x + a1 * h, last.y + a2 * h);
            result.push(last);
          }
        }

        if (pt == null) {
          pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
        }

        isoLineTo(pt.x, pt.y, true);
        isoLineTo(pe.x, pe.y, false);
      }
    };

    mxStyleRegistry.putValue('isometricEdgeStyle', mxEdgeStyle.IsometricConnector);

    //  let graphCreateEdgeHandler = Graph.prototype.createEdgeHandler;
    //  Graph.prototype.createEdgeHandler = function (state, edgeStyle) {
    //  	if (edgeStyle == mxEdgeStyle.IsometricConnector) {
    //  		let handler = new mxElbowEdgeHandler(state);
    //  		handler.snapToTerminals = false;

    //  		return handler;
    //  	}

    //  	return graphCreateEdgeHandler.apply(this, arguments);
    //  };

    // Defines connection points for all shapes
    IsoRectangleShape.prototype.constraints = [];
    IsoCubeShape.prototype.constraints = [];
    CalloutShape.prototype.constraints = [];
    mxRectangleShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true),
      new mxConnectionConstraint(new mxPoint(0.75, 0), true),
      new mxConnectionConstraint(new mxPoint(0, 0.25), true),
      new mxConnectionConstraint(new mxPoint(0, 0.5), true),
      new mxConnectionConstraint(new mxPoint(0, 0.75), true),
      new mxConnectionConstraint(new mxPoint(1, 0.25), true),
      new mxConnectionConstraint(new mxPoint(1, 0.5), true),
      new mxConnectionConstraint(new mxPoint(1, 0.75), true),
      new mxConnectionConstraint(new mxPoint(0.25, 1), true),
      new mxConnectionConstraint(new mxPoint(0.5, 1), true),
      new mxConnectionConstraint(new mxPoint(0.75, 1), true)
    ];
    mxEllipse.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0), true), new mxConnectionConstraint(new mxPoint(1, 0), true),
      new mxConnectionConstraint(new mxPoint(0, 1), true), new mxConnectionConstraint(new mxPoint(1, 1), true),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true), new mxConnectionConstraint(new mxPoint(0.5, 1), true),
      new mxConnectionConstraint(new mxPoint(0, 0.5), true), new mxConnectionConstraint(new mxPoint(1, 0.5))
    ];
    mxLabel.prototype.constraints = mxRectangleShape.prototype.constraints;
    mxImageShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    mxSwimlane.prototype.constraints = mxRectangleShape.prototype.constraints;
    PlusShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    NoteShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    CardShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    CubeShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    FolderShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    InternalStorageShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    DataStorageShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    TapeDataShape.prototype.constraints = mxEllipse.prototype.constraints;
    OrEllipseShape.prototype.constraints = mxEllipse.prototype.constraints;
    SumEllipseShape.prototype.constraints = mxEllipse.prototype.constraints;
    LineEllipseShape.prototype.constraints = mxEllipse.prototype.constraints;
    ManualInputShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    DelayShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    DisplayShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    LoopLimitShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    OffPageConnectorShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    mxCylinder.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.15, 0.05), false),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true),
      new mxConnectionConstraint(new mxPoint(0.85, 0.05), false),
      new mxConnectionConstraint(new mxPoint(0, 0.3), true),
      new mxConnectionConstraint(new mxPoint(0, 0.5), true),
      new mxConnectionConstraint(new mxPoint(0, 0.7), true),
      new mxConnectionConstraint(new mxPoint(1, 0.3), true),
      new mxConnectionConstraint(new mxPoint(1, 0.5), true),
      new mxConnectionConstraint(new mxPoint(1, 0.7), true),
      new mxConnectionConstraint(new mxPoint(0.15, 0.95), false),
      new mxConnectionConstraint(new mxPoint(0.5, 1), true),
      new mxConnectionConstraint(new mxPoint(0.85, 0.95), false)
    ];
    UmlActorShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0.1), false),
      new mxConnectionConstraint(new mxPoint(0.5, 0), false),
      new mxConnectionConstraint(new mxPoint(0.75, 0.1), false),
      new mxConnectionConstraint(new mxPoint(0, 1 / 3), false),
      new mxConnectionConstraint(new mxPoint(0, 1), false),
      new mxConnectionConstraint(new mxPoint(1, 1 / 3), false),
      new mxConnectionConstraint(new mxPoint(1, 1), false),
      new mxConnectionConstraint(new mxPoint(0.5, 0.5), false)
    ];
    ComponentShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true),
      new mxConnectionConstraint(new mxPoint(0.75, 0), true),
      new mxConnectionConstraint(new mxPoint(0, 0.3), true),
      new mxConnectionConstraint(new mxPoint(0, 0.7), true),
      new mxConnectionConstraint(new mxPoint(1, 0.25), true),
      new mxConnectionConstraint(new mxPoint(1, 0.5), true),
      new mxConnectionConstraint(new mxPoint(1, 0.75), true),
      new mxConnectionConstraint(new mxPoint(0.25, 1), true),
      new mxConnectionConstraint(new mxPoint(0.5, 1), true),
      new mxConnectionConstraint(new mxPoint(0.75, 1), true)
    ];
    mxActor.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.5, 0), true),
      new mxConnectionConstraint(new mxPoint(0.25, 0.2), false),
      new mxConnectionConstraint(new mxPoint(0.1, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0, 0.75), true),
      new mxConnectionConstraint(new mxPoint(0.75, 0.25), false),
      new mxConnectionConstraint(new mxPoint(0.9, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 0.75), true),
      new mxConnectionConstraint(new mxPoint(0.25, 1), true),
      new mxConnectionConstraint(new mxPoint(0.5, 1), true),
      new mxConnectionConstraint(new mxPoint(0.75, 1), true)
    ];
    SwitchShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0), false),
      new mxConnectionConstraint(new mxPoint(0.5, 0.25), false),
      new mxConnectionConstraint(new mxPoint(1, 0), false),
      new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.75, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0, 1), false),
      new mxConnectionConstraint(new mxPoint(0.5, 0.75), false),
      new mxConnectionConstraint(new mxPoint(1, 1), false)
    ];
    TapeShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.35), false),
      new mxConnectionConstraint(new mxPoint(0, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0, 0.65), false),
      new mxConnectionConstraint(new mxPoint(1, 0.35), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 0.65), false),
      new mxConnectionConstraint(new mxPoint(0.25, 1), false),
      new mxConnectionConstraint(new mxPoint(0.75, 0), false)
    ];
    StepShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true),
      new mxConnectionConstraint(new mxPoint(0.75, 0), true),
      new mxConnectionConstraint(new mxPoint(0.25, 1), true),
      new mxConnectionConstraint(new mxPoint(0.5, 1), true),
      new mxConnectionConstraint(new mxPoint(0.75, 1), true),
      new mxConnectionConstraint(new mxPoint(0, 0.25), true),
      new mxConnectionConstraint(new mxPoint(0, 0.5), true),
      new mxConnectionConstraint(new mxPoint(0, 0.75), true),
      new mxConnectionConstraint(new mxPoint(1, 0.25), true),
      new mxConnectionConstraint(new mxPoint(1, 0.5), true),
      new mxConnectionConstraint(new mxPoint(1, 0.75), true)
    ];
    mxLine.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.75, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false)
    ];
    LollipopShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.5, 0), false),
      new mxConnectionConstraint(new mxPoint(0.5, 1), false)
    ];
    mxDoubleEllipse.prototype.constraints = mxEllipse.prototype.constraints;
    mxRhombus.prototype.constraints = mxEllipse.prototype.constraints;
    mxTriangle.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.25), true),
      new mxConnectionConstraint(new mxPoint(0, 0.5), true),
      new mxConnectionConstraint(new mxPoint(0, 0.75), true),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true),
      new mxConnectionConstraint(new mxPoint(0.5, 1), true),
      new mxConnectionConstraint(new mxPoint(1, 0.5), true)
    ];
    mxHexagon.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.375, 0), true),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true),
      new mxConnectionConstraint(new mxPoint(0.625, 0), true),
      new mxConnectionConstraint(new mxPoint(0, 0.25), true),
      new mxConnectionConstraint(new mxPoint(0, 0.5), true),
      new mxConnectionConstraint(new mxPoint(0, 0.75), true),
      new mxConnectionConstraint(new mxPoint(1, 0.25), true),
      new mxConnectionConstraint(new mxPoint(1, 0.5), true),
      new mxConnectionConstraint(new mxPoint(1, 0.75), true),
      new mxConnectionConstraint(new mxPoint(0.375, 1), true),
      new mxConnectionConstraint(new mxPoint(0.5, 1), true),
      new mxConnectionConstraint(new mxPoint(0.625, 1), true)
    ];
    mxCloud.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0.25), false),
      new mxConnectionConstraint(new mxPoint(0.4, 0.1), false),
      new mxConnectionConstraint(new mxPoint(0.16, 0.55), false),
      new mxConnectionConstraint(new mxPoint(0.07, 0.4), false),
      new mxConnectionConstraint(new mxPoint(0.31, 0.8), false),
      new mxConnectionConstraint(new mxPoint(0.13, 0.77), false),
      new mxConnectionConstraint(new mxPoint(0.8, 0.8), false),
      new mxConnectionConstraint(new mxPoint(0.55, 0.95), false),
      new mxConnectionConstraint(new mxPoint(0.875, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.96, 0.7), false),
      new mxConnectionConstraint(new mxPoint(0.625, 0.2), false),
      new mxConnectionConstraint(new mxPoint(0.88, 0.25), false)
    ];
    ParallelogramShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    TrapezoidShape.prototype.constraints = mxRectangleShape.prototype.constraints;
    DocumentShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true),
      new mxConnectionConstraint(new mxPoint(0.75, 0), true),
      new mxConnectionConstraint(new mxPoint(0, 0.25), true),
      new mxConnectionConstraint(new mxPoint(0, 0.5), true),
      new mxConnectionConstraint(new mxPoint(0, 0.75), true),
      new mxConnectionConstraint(new mxPoint(1, 0.25), true),
      new mxConnectionConstraint(new mxPoint(1, 0.5), true),
      new mxConnectionConstraint(new mxPoint(1, 0.75), true)
    ];
    mxArrow.prototype.constraints = null;
    TeeShape.prototype.constraints = null;
    CornerShape.prototype.constraints = null;
    CrossbarShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0), false),
      new mxConnectionConstraint(new mxPoint(0, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0, 1), false),
      new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.5, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.75, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 0), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 1), false)
    ];

    SingleArrowShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false)
    ];
    DoubleArrowShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false)
    ];
    CrossShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.5, 0), false),
      new mxConnectionConstraint(new mxPoint(0.5, 1), false)
    ];
    UmlLifeline.prototype.constraints = null;
    OrShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.25), false),
      new mxConnectionConstraint(new mxPoint(0, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0, 0.75), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.7, 0.1), false),
      new mxConnectionConstraint(new mxPoint(0.7, 0.9), false)
    ];
    XorShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.175, 0.25), false),
      new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.175, 0.75), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false),
      new mxConnectionConstraint(new mxPoint(0.7, 0.1), false),
      new mxConnectionConstraint(new mxPoint(0.7, 0.9), false)
    ];
    RequiredInterfaceShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false)
    ];
    ProvidedRequiredInterfaceShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.5), false),
      new mxConnectionConstraint(new mxPoint(1, 0.5), false)
    ];

  } catch (e) {
    console.log(e);
  }

}());